news 2026/9/6 10:38:04

一种奇特的干掉 if..else 方式,Spring Boot+Aviator+Aop 挺有趣!

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一种奇特的干掉 if..else 方式,Spring Boot+Aviator+Aop 挺有趣!

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中,像使用内置函数一样使用自定义函数:

// 注册 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); } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 20:14:42

那些让你起飞的计算机基础知识!

那些让你起飞的计算机基础知识&#xff01;在计算机科学领域&#xff0c;打好基础就像为飞机安装引擎——它能让你快速起飞&#xff0c;高效解决问题&#xff0c;并在职业生涯中稳步攀升。这些基础知识不仅是入门的关键&#xff0c;更是进阶的基石。下面&#xff0c;我将列出一…

作者头像 李华
网站建设 2026/9/4 5:56:40

双冠加冕!C#摘得2025 TIOBE年度语言,凭实力领跑编程新时代

当TIOBE在2026年初正式揭晓2025年度编程语言时&#xff0c;C#以同比最大增幅的亮眼表现强势登顶&#xff0c;三年内第二次斩获这一行业殊荣&#xff0c;用实力打破了外界对编程语言格局的固有认知 。从曾经的Windows专属语言到如今跨平台生态的领军者&#xff0c;从微软内部主导…

作者头像 李华
网站建设 2026/9/2 20:14:05

Git Commit规范检查工具集成GLM-4.6V-Flash-WEB提交日志分析

Git Commit规范检查工具集成GLM-4.6V-Flash-WEB提交日志分析 在现代软件开发中&#xff0c;一个看似微不足道的 git commit -m "fix bug" 提交记录&#xff0c;可能背后隐藏着关键逻辑变更。而当项目成员超过十人、每日提交上百次时&#xff0c;如何确保每一次提交都…

作者头像 李华
网站建设 2026/9/2 21:47:48

从单机缓存到分布式高可用缓存与一致性体系落地的互联网系统工程实践随笔与多语言语法思考

在高并发互联网系统中&#xff0c;缓存是核心性能保障&#xff0c;但单机缓存在多实例、多节点环境下容易出现数据不一致、脏读或缓存雪崩问题。本文围绕分布式缓存高可用与一致性策略展开&#xff0c;结合多语言代码示例&#xff0c;分享从单机缓存到全局缓存体系落地的工程实…

作者头像 李华
网站建设 2026/9/4 15:47:50

Chromedriver下载地址汇总:助力GLM-4.6V-Flash-WEB自动化测试

Chromedriver下载地址汇总&#xff1a;助力GLM-4.6V-Flash-WEB自动化测试 在AI模型快速迭代的今天&#xff0c;一个常见的工程难题浮出水面&#xff1a;如何确保每次更新后&#xff0c;视觉语言模型依然能稳定响应前端请求&#xff1f;尤其是像 GLM-4.6V-Flash-WEB 这类面向Web…

作者头像 李华
网站建设 2026/9/2 22:06:57

Dify access_token配置终极手册(附生产环境配置模板)

第一章&#xff1a;Dify access_token配置概述在使用 Dify 平台进行应用开发和集成时&#xff0c;access_token 是实现身份验证与授权访问的核心凭证。它用于标识调用者的身份&#xff0c;并决定其对 API 资源的访问权限范围。正确配置 access_token 可确保系统间安全、稳定地通…

作者头像 李华