⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 网络 「网络」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

aviator本来是一个轻量级、高性能的基于JVM的表达式引擎。不过从5.0.0版本开始,aviator升级成为了aviatorScript,成为一个高性能、轻量级寄宿于 JVM (包括 Android 平台)之上的脚本语言。

根据官网的介绍,aviator支持的主要特性:

  1. 支持数字、字符串、正则表达式、布尔值、正则表达式等基本类型,完整支持所有 Java 运算符及优先级等。
  2. 函数是一等公民,支持闭包和函数式编程
  3. 内置 bigint/decimal类型用于大整数和高精度运算,支持运算符重载得以让这些类型使用普通的算术运算符 +-*/参与运算。
  4. 完整的脚本语法支持,包括多行数据、条件语句、循环语句、词法作用域和异常处理等。
  5. 函数式编程结合 Sequence 抽象,便捷处理任何集合。
  6. 轻量化的模块系统。
  7. 多种方式,方便地调用 Java 方法,完整支持 Java 脚本 API(方便从 Java 调用脚本)。
  8. 丰富的定制选项,可作为安全的语言沙箱和全功能语言使用。
  9. 轻量化,高性能,ASM 模式下通过直接将脚本翻译成 JVM 字节码,解释模式可运行于 Android 等非标 Java 平台。

使用场景包括:

  1. 规则判断及规则引擎
  2. 公式计算
  3. 动态脚本控制
  4. 集合数据 ELT 等

Aviator的限制

  • 没有if else、do while等语句,没有赋值语句,仅支持逻辑表达式、算术表达式、三元表达式和正则匹配。
  • 不支持八进制数字字面量,仅支持十进制和十六进制数字字面量。

aviator基本使用

基本表达式

要使用aviator,只需要添加相应依赖:

<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>5.3.3</version>
</dependency>

然后就可以进行表达式求值了:

// 返回值为16
Long r = (Long) AviatorEvaluator.execute("2 * (3 + 5)");

为了提升性能,往往先编译表达式,然后可以反复执行,进行表达式求值:

Expression expression = AviatorEvaluator.compile("2 * (3 + 5)");
Long r = (Long) expression.execute();

aviator支持数字、字符串、布尔值等基本数据类型,数字类型值都当作long或double类型处理。关注工众号:码猿技术专栏,回复关键词:1111 获取阿里内部java性能调优手册!所以上面例子的求值结果是Long。

aviator表达式支持大部分的运算操作符,如常用的算术运算操作符(+、-、*、/、%)、逻辑运算操作符(&&、||、!)、比较运算操作符(>、>=、==、!=、<、<=)、位运算操作符(&、|、^、<<、>>)和优先级操作符,还支持三元操作表达(?:)、正则表达式(=~)。

一些例子:

// 返回 hello world
String r = (String) AviatorEvaluator.execute("'hello' + ' world'");

// 返回 true
Boolean r = (Boolean) AviatorEvaluator.execute("100 > 80 && 30 < 40");

// 三元表达式,返回 30
Long r = (Long) AviatorEvaluator.execute("100 > 80 ? 30 : 40");

// 正则表达式,正则表达式放在//之间,返回 true
Boolean r = (Boolean) AviatorEvaluator.execute("'hello' =~ /[\\w]+/");

表达式变量

跟其他表达式引擎一样,aviator也是支持表达式求值时传入参数的:

Long a = 12L;
Boolean r = (Boolean) AviatorEvaluator.exec("a > 10", a);

参数也可以是一个列表,如下:

List<Long> a = new ArrayList<>();
a.add(12L);
a.add(20L);
Boolean r = (Boolean) AviatorEvaluator.exec("a[0] > 10", a);

也可以是一个对象:

public static class Person {
private String name;
private Integer age;
}

Person a = new Person("movee", 25);
Boolean r = (Boolean) AviatorEvaluator.exec("a.age > 10", a);

跟一般地,aviator会将参数放到一个map中

Map<String, Object> env = new HashMap<>();
env.put("person", new Person("movee", 25));
env.put("a", 20L);
Object result = AviatorEvaluator.execute("person.name", env);

这样一来,aviator可以非常方便的从json字符串中提取子json字符串

String jsonStr = """
{
"a": {
"b": [
{
"x": 3
},
{
"x": 4
}
]
}
}
""";


JSONObject jsonObj = new JSONObject(jsonStr);
// 结果返回 3
Object value = AviatorEvaluator.execute("a.b[0]['x']", jsonObj.toMap());

使用函数

aviator已经提供了很多开箱即用的函数了:

// 返回4
Long r = (Long) AviatorEvaluator.execute("math.round(4.3)");

// 返回5
Long r = (Long) AviatorEvaluator.execute("string.length('hello')");

// 返回一个ArrayList:[1,2,3]
Object r = AviatorEvaluator.execute("seq.list(1,2,3)");

更详细的内置函数列表请参考:aviator函数库列表

我们也可以自定义一个java函数,自己编写一个类,继承aviator的AbstractFunction类,然后实现相应的方法即可:

public class AddFunction extends AbstractFunction {

/**
* 函数实现的功能
* @param env 参数
* @param arg1 函数的第一个参数
* @param arg2 函数的第二个参数
* @return 返回值
*/
@Override
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
long num1 = FunctionUtils.getNumberValue(arg1, env).longValue();
long num2 = FunctionUtils.getNumberValue(arg2, env).longValue();
return AviatorLong.valueOf(num1+num2);
}

/**
* 注册到aviator的名字
* @return 函数名字
*/
@Override
public String getName() {
return"add";
}
}

然后就可以注册到aviator中,关注工众号:码猿技术专栏,回复关键词:1111 获取阿里内部java性能调优手册!像使用内置函数一样使用自定义函数:

// 注册
AviatorEvaluator.addFunction(new AddFunction());

// 使用
long sum = (Long) AviatorEvaluator.getInstance().execute("add(3,4)");

aviatorScript脚本

aviator已经升级为一个脚本语言,所以不仅仅能进行表达式求值,还可以执行脚本程序。

// 返回1
Object r = AviatorEvaluator.execute("if (true) { return 1; } else { return 2; }");

aviatorScript脚本一般放到独立的脚本文件中,文件名后缀一般为.av

例如,我们编写一个hello.av脚本文件,内容为:

if (a > 10) {
return 10;
} else {
return a;
}

然后就可以执行该脚本文件,并传入参数a的值:

Map<String, Object> env = new HashMap<>();
env.put("a", 30);
Expression exp = AviatorEvaluator.getInstance().compileScript("./hello.av", true);
Object result = exp.execute(env);

实操

利用aviator+aop实现参数校验

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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>

controller

在方法上加上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;

@RestController
publicclass 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

单个规则注解

package com.et.annotation;

import java.lang.annotation.*;


@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
//add more on a method
@Repeatable(CheckContainer.class)
public @interface 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.*;


@Aspect
@Configuration
publicclass AopConfig {

/**
* Aspects monitor multiple annotations, because one annotation is Check and multiple annotations are compiled to CheckContainer
*/
@Pointcut("@annotation(com.et.annotation.CheckContainer) || @annotation(com.et.annotation.Check)")
public void pointcut() {
}

@Before("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) {
thrownew UserFriendlyException(msg);
}
}
returnnull;
}
}

全局异常拦截

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
publicclass DefaultGlobalExceptionHandler extends ResponseEntityExceptionHandler {
privatestaticfinal 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());
returnsuper.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 userFriendlyException = (UserFriendlyException) ex;
is5xxServerError = userFriendlyException.getHttpStatusCode() >= 500;
httpStatus = HttpStatus.valueOf(userFriendlyException.getHttpStatusCode());
httpResult = HttpResult.failure(userFriendlyException.getErrorCode(), userFriendlyException.getMessage());
} elseif (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());
}
returnnew ResponseEntity<>(httpResult, httpStatus);
}

}
文章目录
  1. 1. Aviator的限制
  2. 2. aviator基本使用
    1. 2.1. 基本表达式
    2. 2.2. 表达式变量
    3. 2.3. 使用函数
    4. 2.4. aviatorScript脚本
  3. 3. 实操
    1. 3.1. pom.xml
    2. 3.2. controller
    3. 3.3. annotation
    4. 3.4. 全局异常拦截