Eliminate if‑else with Spring Boot, Aviator, and AOP: A Smooth Approach
This article introduces Aviator, a lightweight JVM‑based expression engine, explains its features and limitations, and demonstrates how to integrate it with Spring Boot and AOP to replace traditional if‑else validation logic using custom annotations and script files.
Introducing Aviator
Aviator started as a lightweight, high‑performance JVM expression engine. Since version 5.0.0 it has evolved into AviatorScript, a full‑featured script language that runs on the JVM, including Android.
Key features listed on the official site include:
Support for basic types such as numbers, strings, regex, booleans, and full Java operator precedence.
First‑class functions with closures and functional programming support.
Built‑in bigint / decimal types with operator overloading (e.g., +-*/).
Complete script syntax: multiline data, conditionals, loops, lexical scopes, and exception handling.
Functional programming combined with a Sequence abstraction for easy collection handling.
Lightweight module system.
Multiple ways to call Java methods, fully supporting the Java script API.
Rich customization options, usable as a secure sandbox or a full‑featured language.
High performance: ASM mode compiles scripts directly to JVM bytecode; an interpreter mode works on non‑standard Java platforms such as Android.
Typical use cases are rule evaluation, formula calculation, dynamic script control, and ELT‑style collection processing.
Aviator Limitations
No if / else, do‑while, or assignment statements; only logical, arithmetic, ternary, and regex expressions are supported.
Octal literals are not allowed; only decimal and hexadecimal literals are supported.
Basic Usage
Dependency
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>5.3.3</version>
</dependency>Evaluating an Expression
// 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 treats all numbers as long or double. The example above therefore returns a Long.
The engine supports most operators, including arithmetic ( + - * / %), logical ( && || !), comparison ( > >= == != < <=), bitwise ( & | ^ << >>), ternary ( ?:), and regex ( =~).
Some concrete examples:
// 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
Variables can be passed to the evaluator just like other engines:
Long a = 12L;
Boolean r = (Boolean) AviatorEvaluator.exec("a > 10", a);Lists and maps are also supported:
List<Long> a = new ArrayList<>();
a.add(12L);
a.add(20L);
Boolean r = (Boolean) AviatorEvaluator.exec("a[0] > 10", a);Objects can be used as well:
public class Person {
private String name;
private Integer age;
}
Person p = new Person("movee", 25);
Boolean r = (Boolean) AviatorEvaluator.exec("p.age > 10", p);When a map is supplied, Aviator can easily extract values from JSON structures:
Map<String, Object> env = new HashMap<>();
env.put("person", new Person("movee", 25));
env.put("a", 20L);
Object result = AviatorEvaluator.execute("person.name", env);Using Functions
Aviator provides many built‑in functions. Examples:
// Rounding
Long r = (Long) AviatorEvaluator.execute("math.round(4.3)");
// String length
Long r = (Long) AviatorEvaluator.execute("string.length('hello')");
// Create a list
Object r = AviatorEvaluator.execute("seq.list(1,2,3)");Custom functions can be created by extending AbstractFunction and registering them with AviatorEvaluator.addFunction(new MyFunction()).
AviatorScript
Since Aviator 5.x, scripts can be written in files with the .av suffix. A script can contain full control flow, for example:
Object r = AviatorEvaluator.execute("if (true) { return 1; } else { return 2; }");A typical script file ( hello.av) might look like:
if (a > 10) {
return 10;
} else {
return a;
}It can be compiled and executed with a parameter map:
Map<String, Object> env = new HashMap<>();
env.put("a", 30);
Expression exp = AviatorEvaluator.getInstance().compileScript("./hello.av", true);
Object result = exp.execute(env);Practical Example: Replacing if‑else Validation in Spring Boot
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
<parent>
<artifactId>springboot-demo</artifactId>
<groupId>com.et</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<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>
<!-- other dependencies omitted for brevity -->
</dependencies>
</project>Controller with Validation Annotations
package com.et.controller;
import com.et.annotation.Check;
import com.et.exception.HttpResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class HelloWorldController {
@RequestMapping("/hello")
public Map<String, Object> showHelloWorld() {
Map<String, Object> map = new HashMap<>();
map.put("msg", "HelloWorld");
return map;
}
@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();
}
}Annotation Definitions
package com.et.annotation;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(CheckContainer.class)
public @interface Check {
String ex() default "";
String msg() default "";
}
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckContainer {
Check[] value();
}AOP Configuration to Process @Check
package com.et.annotation;
import com.et.exception.UserFriendlyException;
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.runtime.type.AviatorObject;
import com.googlecode.aviator.runtime.type.AviatorLong;
import com.googlecode.aviator.runtime.type.FunctionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.util.StringUtils;
import java.lang.reflect.Method;
import java.util.*;
@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 discoverer = new LocalVariableTableParameterNameDiscoverer();
String[] paramNames = discoverer.getParameterNames(method);
CheckContainer container = method.getDeclaredAnnotation(CheckContainer.class);
List<Check> checks = new ArrayList<>();
if (container != null) {
checks.addAll(Arrays.asList(container.value()));
} else {
Check single = method.getDeclaredAnnotation(Check.class);
checks.add(single);
}
for (Check check : checks) {
String ex = check.ex().replaceAll("null", "nil");
String msg = check.msg();
if (!StringUtils.hasText(msg)) {
msg = "server exception...";
}
Map<String, Object> map = new HashMap<>(16);
for (int i = 0; i < paramNames.length; i++) {
if (i >= args.length) continue;
map.put(paramNames[i], args[i]);
}
Boolean result = (Boolean) AviatorEvaluator.execute(ex, map);
if (!result) {
throw new UserFriendlyException(msg);
}
}
return null;
}
}Global Exception Handler
package com.et.exception;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import javax.servlet.http.HttpServletRequest;
@Configuration
@ControllerAdvice
public class DefaultGlobalExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultGlobalExceptionHandler.class);
@Override
protected ResponseEntity<Object> 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<Object> handleException(HttpServletRequest request, Exception ex) {
boolean is5xxServerError;
HttpStatus httpStatus;
HttpResult httpResult;
if (ex instanceof UserFriendlyException) {
UserFriendlyException ufe = (UserFriendlyException) ex;
is5xxServerError = ufe.getHttpStatusCode() >= 500;
httpStatus = HttpStatus.valueOf(ufe.getHttpStatusCode());
httpResult = HttpResult.failure(ufe.getErrorCode(), ufe.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);
}
}The above setup shows how Aviator expressions can be embedded in custom @Check annotations, processed by an AOP advice, and turned into runtime validation without writing explicit if‑else blocks.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer XiaoFu
xiaofucode.com – a programmer learning guide driven by the pursuit of profit
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.
