Aviator是一个高性能、轻量级的java语言实现的表达式求值引擎,主要用于各种表达式的动态求值。现在已经有很多开源可用的java表达式求值引擎,为什么还需要Avaitor呢? Aviator的设计目标是轻量级
和高性能
,相比于Groovy、JRuby的笨重,Aviator非常小,加上依赖包也才450K,不算依赖包的话只有70K;当然,Aviator的语法是受限的,它不是一门完整的语言,而只是语言的一小部分集合。 其次,Aviator的实现思路与其他轻量级的求值器很不相同,其他求值器一般都是通过解释的方式运行,而Aviator则是直接将表达式编译成Java字节码
,交给JVM去执行。简单来说,Aviator的定位是介于Groovy这样的重量级脚本语言和IKExpression这样的轻量级表达式引擎之间
支持大部分运算操作符,包括算术操作符、关系运算符、逻辑操作符、位运算符、正则匹配操作符(=~)、三元表达式?: ,并且支持操作符的优先级和括号强制优先级,具体请看后面的操作符列表。
支持大整数和精度运算(2.3.0版本引入)
支持函数调用和自定义函数
内置支持正则表达式匹配,类似Ruby、Perl的匹配语法,并且支持类Ruby的$digit指向匹配分组。
自动类型转换,当执行操作的时候,会自动判断操作数类型并做相应转换,无法转换即抛异常。
支持传入变量,支持类似a.b.c的嵌套变量访问。
函数式风格的seq库,操作集合和数组
性能优秀
没有if else、do while等语句,没有赋值语句,仅支持逻辑表达式、算术表达式、三元表达式和正则匹配。
不支持八进制数字字面量,仅支持十进制和十六进制数字字面量。
规则判断以及规则引擎
公式计算
动态脚本控制
利用aviator+aop实现参数校验
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot-demo</artifactId>
<groupId>com.et</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Aviator</artifactId>
<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-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--AOP-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--Aviator-->
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.56</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
</dependencies>
</project>
在方法上加伤aviator校验规则
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;
public class HelloWorldController {
public Map<String, Object> showHelloWorld(){
Map<String, Object> map = new HashMap<>();
map.put("msg", "HelloWorld");
return map;
}
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();
}
}
单个规则注解
package com.et.annotation;
import java.lang.annotation.*;
//add more on a method
public Check {
String ex() default "";
String msg() default "";
}
多个规则注解
package com.et.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckContainer {
Check[] value();
}
AOP拦截注解
package com.et.annotation;
import com.et.exception.UserFriendlyException;
import com.googlecode.aviator.AviatorEvaluator;
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.*;
public class AopConfig {
/**
* Aspects monitor multiple annotations, because one annotation is Check and multiple annotations are compiled to CheckContainer
*/
public void pointcut() {
}
public Object before(JoinPoint point) {
//get params
Object[] args = point.getArgs();
//get param name
Method method = ((MethodSignature) point.getSignature()).getMethod();
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String[] paramNames = u.getParameterNames(method);
CheckContainer checkContainer = method.getDeclaredAnnotation(CheckContainer.class);
List<Check> value = new ArrayList<>();
if (checkContainer != null) {
value.addAll(Arrays.asList(checkContainer.value()));
} else {
Check check = method.getDeclaredAnnotation(Check.class);
value.add(check);
}
for (int i = 0; i < value.size(); i++) {
Check check = value.get(i);
String ex = check.ex();
//In the rule engine, null is represented by nil
ex = ex.replaceAll("null", "nil");
String msg = check.msg();
if (StringUtils.isEmpty(msg)) {
msg = "server exception...";
}
Map<String, Object> map = new HashMap<>(16);
for (int j = 0; j < paramNames.length; j++) {
//Prevent index out of bounds
if (j > args.length) {
continue;
}
map.put(paramNames[j], args[j]);
}
Boolean result = (Boolean) AviatorEvaluator.execute(ex, map);
if (!result) {
throw new UserFriendlyException(msg);
}
}
return null;
}
}
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;
public class DefaultGlobalExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultGlobalExceptionHandler.class);
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, 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);
}
(Exception.class)
protected ResponseEntity handleException(HttpServletRequest request, Exception ex) {
boolean is5xxServerError;
HttpStatus httpStatus;
HttpResult httpResult;
if (ex instanceof UserFriendlyException) {
UserFriendlyException userFriendlyException = (UserFriendlyException) ex;
is5xxServerError = userFriendlyException.getHttpStatusCode() >= 500;
httpStatus = HttpStatus.valueOf(userFriendlyException.getHttpStatusCode());
httpResult = HttpResult.failure(userFriendlyException.getErrorCode(), userFriendlyException.getMessage());
} else if (ex instanceof IllegalArgumentException) {
// Spring assertions are used in parameter judgment. requireTrue will throw an IllegalArgumentException. The client cannot handle 5xx exceptions, so 200 is still returned.
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);
}
}
以上只是一些关键代码,所有代码请参见下面代码仓库
https://github.com/Harries/springboot-demo(aviator)
启动Spring Boot应用
访问 http://127.0.0.1:8088/simple?name=jack&age=12
返回校验信息{"status":false,"code":4,"message":"Age must be over 18 years old","entry":null}
https://github.com/killme2008/aviatorscript/blob/master/README-EN.md