AviatorScript: Features, Usage, and Spring Boot Integration for Expression Evaluation and Validation
This article introduces AviatorScript, a lightweight high‑performance JVM‑based scripting language, outlines its core features and limitations, demonstrates how to add it to a Maven project, shows expression evaluation, variable handling, custom functions, script files, and provides a practical Spring Boot AOP validation example with full code snippets.
Aviator originally was a lightweight, high‑performance expression engine for the JVM; from version 5.0.0 it evolved into AviatorScript, a full‑featured script language that runs on the JVM (including Android).
Key features include support for basic types (numbers, strings, regex, booleans), first‑class functions with closures, bigint/decimal types with operator overloading ( +-*/ ), complete script syntax (multiline data, conditionals, loops, lexical scope, exception handling), sequence abstraction for functional collection processing, a lightweight module system, easy Java method invocation, sandbox options, and ASM‑based compilation that translates scripts directly to JVM bytecode for high performance.
Typical scenarios are rule engines, formula calculations, dynamic script control, and ELT‑style collection processing.
Limitations : no if‑else , do‑while , or assignment statements; only logical, arithmetic, ternary, and regex expressions are supported, and octal literals are not allowed (only decimal and hexadecimal).
Basic usage : add the Maven dependency.
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>5.3.3</version>
</dependency>Then evaluate an expression directly:
// returns 16
Long r = (Long) AviatorEvaluator.execute("2 * (3 + 5)");For better performance, compile the expression first and reuse it:
Expression expression = AviatorEvaluator.compile("2 * (3 + 5)");
Long r = (Long) expression.execute();Aviator supports numbers, strings, booleans, and treats numeric values as long or double . Example of various operators:
// string concatenation
String r = (String) AviatorEvaluator.execute("'hello' + ' world'");
// logical expression
Boolean r = (Boolean) AviatorEvaluator.execute("100 > 80 && 30 < 40");
// ternary expression
Long r = (Long) AviatorEvaluator.execute("100 > 80 ? 30 : 40");
// regex match
Boolean r = (Boolean) AviatorEvaluator.execute("'hello' =~ /[\\w]+/");Expression variables can be passed as parameters:
Long a = 12L;
Boolean r = (Boolean) AviatorEvaluator.exec("a > 10", a);
List
a = new ArrayList<>();
a.add(12L);
a.add(20L);
Boolean r = (Boolean) AviatorEvaluator.exec("a[0] > 10", a);
public class Person {
private String name;
private Integer age;
}
Person p = new Person("movee", 25);
Boolean r = (Boolean) AviatorEvaluator.exec("p.age > 10", p);
Map
env = new HashMap<>();
env.put("person", new Person("movee", 25));
env.put("a", 20L);
Object result = AviatorEvaluator.execute("person.name", env);Aviator can also extract values from JSON strings:
String jsonStr = "{\"a\":{\"b\":[{\"x\":3},{\"x\":4}]}}";
JSONObject jsonObj = new JSONObject(jsonStr);
Object value = AviatorEvaluator.execute("a.b[0]['x']", jsonObj.toMap()); // returns 3Built‑in functions such as math.round , string.length , and seq.list are available, and custom functions can be created by extending AbstractFunction :
public class AddFunction extends AbstractFunction {
@Override
public AviatorObject call(Map
env, AviatorObject arg1, AviatorObject arg2) {
long num1 = FunctionUtils.getNumberValue(arg1, env).longValue();
long num2 = FunctionUtils.getNumberValue(arg2, env).longValue();
return AviatorLong.valueOf(num1 + num2);
}
@Override
public String getName() { return "add"; }
}
AviatorEvaluator.addFunction(new AddFunction());
Long sum = (Long) AviatorEvaluator.getInstance().execute("add(3,4)");AviatorScript files usually have the .av suffix. Example hello.av :
if (a > 10) {
return 10;
} else {
return a;
}Execute the script with parameters:
Map
env = new HashMap<>();
env.put("a", 30);
Expression exp = AviatorEvaluator.getInstance().compileScript("./hello.av", true);
Object result = exp.execute(env);Practical example: using Aviator + AOP for parameter validation in Spring Boot
pom.xml snippet (relevant dependencies):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>3.3.0</version>
</dependency>Controller method annotated with multiple @Check annotations:
@RestController
public class HelloWorldController {
@GetMapping("/simple")
@Check(ex = "name != null", msg = "Name cannot be empty")
@Check(ex = "age != null", msg = "Age cannot be empty")
@Check(ex = "age > 18", msg = "Age must be over 18 years old")
@Check(ex = "phone != null", msg = "phone cannot be empty")
@Check(ex = "phone =~ /^(1)[0-9]{10}$/", msg = "The phone number format is incorrect")
@Check(ex = "string.startsWith(phone,\"1\")", msg = "The phone number must start with 1")
@Check(ex = "idCard != null", msg = "ID number cannot be empty")
@Check(ex = "idCard =~ /^[1-9]\\d{5}[1-9]\\d{3}((0[1-9])||(1[0-2]))((0[1-9])||(1\\d)||(2\\d)||(3[0-1]))\\d{3}([0-9]||X)$/", msg = "ID number format is incorrect")
@Check(ex = "gender == 1", msg = "sex")
@Check(ex = "date =~ /^[1-9][0-9]{3}-((0)[1-9]|(1)[0-2])-((0)[1-9]|[1,2][0-9]|(3)[0,1])$/", msg = "Wrong date format")
@Check(ex = "date > '2019-12-20 00:00:00:00'", msg = "The date must be greater than 2019-12-20")
public HttpResult simple(String name, Integer age, String phone, String idCard, String date) {
System.out.println("name = " + name);
System.out.println("age = " + age);
System.out.println("phone = " + phone);
System.out.println("idCard = " + idCard);
System.out.println("date = " + date);
return HttpResult.success();
}
}Definition of @Check annotation and its container:
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(CheckContainer.class)
public @interface Check {
String ex() default "";
String msg() default "";
}
public @interface CheckContainer {
Check[] value();
}AOP configuration that intercepts methods annotated with @Check or @CheckContainer , builds a parameter map, evaluates each expression with Aviator, and throws UserFriendlyException on failure:
@Aspect
@Configuration
public class AopConfig {
@Pointcut("@annotation(com.et.annotation.CheckContainer) || @annotation(com.et.annotation.Check)")
public void pointcut() {}
@Before("pointcut()")
public Object before(JoinPoint point) {
Object[] args = point.getArgs();
Method method = ((MethodSignature) point.getSignature()).getMethod();
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String[] paramNames = u.getParameterNames(method);
CheckContainer container = method.getDeclaredAnnotation(CheckContainer.class);
List
checks = new ArrayList<>();
if (container != null) {
checks.addAll(Arrays.asList(container.value()));
} else {
checks.add(method.getDeclaredAnnotation(Check.class));
}
for (Check check : checks) {
String ex = check.ex().replaceAll("null", "nil");
String msg = StringUtils.isEmpty(check.msg()) ? "server exception..." : check.msg();
Map
map = new HashMap<>(16);
for (int i = 0; i < paramNames.length && i < args.length; i++) {
map.put(paramNames[i], args[i]);
}
Boolean result = (Boolean) AviatorEvaluator.execute(ex, map);
if (!result) {
throw new UserFriendlyException(msg);
}
}
return null;
}
}Global exception handler that formats validation and server errors into a unified HttpResult response:
@Configuration
@ControllerAdvice
public class DefaultGlobalExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultGlobalExceptionHandler.class);
@Override
protected ResponseEntity
handleExceptionInternal(Exception ex, @Nullable Object body,
HttpHeaders headers, HttpStatus status, WebRequest request) {
HttpResult httpResult = HttpResult.failure(status.is5xxServerError() ?
ErrorCode.serverError.getDesc() : ErrorCode.paramError.getDesc());
LOGGER.error("handleException, ex caught, contextPath={}, httpResult={}, ex.msg={}",
request.getContextPath(), JSON.toJSONString(httpResult), ex.getMessage());
return super.handleExceptionInternal(ex, httpResult, headers, status, request);
}
@ExceptionHandler(Exception.class)
protected ResponseEntity handleException(HttpServletRequest request, Exception ex) {
boolean is5xxServerError;
HttpStatus httpStatus;
HttpResult httpResult;
if (ex instanceof UserFriendlyException) {
UserFriendlyException ue = (UserFriendlyException) ex;
is5xxServerError = ue.getHttpStatusCode() >= 500;
httpStatus = HttpStatus.valueOf(ue.getHttpStatusCode());
httpResult = HttpResult.failure(ue.getErrorCode(), ue.getMessage());
} else if (ex instanceof IllegalArgumentException) {
httpStatus = HttpStatus.OK;
is5xxServerError = false;
httpResult = HttpResult.failure("Parameter verification error or data abnormality!");
} else {
httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
is5xxServerError = true;
httpResult = HttpResult.failure(ErrorCode.serverError.getDesc());
}
if (is5xxServerError) {
LOGGER.error("handleException, ex caught, uri={}, httpResult={}", request.getRequestURI(),
JSON.toJSONString(httpResult), ex);
} else {
LOGGER.error("handleException, ex caught, uri={}, httpResult={}, ex.msg={}",
request.getRequestURI(), JSON.toJSONString(httpResult), ex.getMessage());
}
return new ResponseEntity<>(httpResult, httpStatus);
}
}Finally, the article includes a short promotional paragraph for a new booklet covering 40 common backend pain points and a set of recommended reading links.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.