SpringBoot集成Aviator实现参数校验的代码工程
作者:HBLOGA
Aviator是一个高性能、轻量级的java语言实现的表达式求值引擎,主要用于各种表达式的动态求值,本文给大家详细介绍了SpringBoot集成Aviator实现参数校验的方法,并通过代码示例讲解的非常详细,需要的朋友可以参考下
1.什么是aviator?
Aviator是一个高性能、轻量级的java语言实现的表达式求值引擎,主要用于各种表达式的动态求值。现在已经有很多开源可用的java表达式求值引擎,为什么还需要Avaitor呢? Aviator的设计目标是轻量级
和高性能
,相比于Groovy、JRuby的笨重,Aviator非常小,加上依赖包也才450K,不算依赖包的话只有70K;当然,Aviator的语法是受限的,它不是一门完整的语言,而只是语言的一小部分集合。 其次,Aviator的实现思路与其他轻量级的求值器很不相同,其他求值器一般都是通过解释的方式运行,而Aviator则是直接将表达式编译成Java字节码
,交给JVM去执行。简单来说,Aviator的定位是介于Groovy这样的重量级脚本语言和IKExpression这样的轻量级表达式引擎之间
Aviator的特性
- 支持大部分运算操作符,包括算术操作符、关系运算符、逻辑操作符、位运算符、正则匹配操作符(=~)、三元表达式?: ,并且支持操作符的优先级和括号强制优先级,具体请看后面的操作符列表。
- 支持大整数和精度运算(2.3.0版本引入)
- 支持函数调用和自定义函数
- 内置支持正则表达式匹配,类似Ruby、Perl的匹配语法,并且支持类Ruby的$digit指向匹配分组。
- 自动类型转换,当执行操作的时候,会自动判断操作数类型并做相应转换,无法转换即抛异常。
- 支持传入变量,支持类似a.b.c的嵌套变量访问。
- 函数式风格的seq库,操作集合和数组
- 性能优秀
Aviator的限制
- 没有if else、do while等语句,没有赋值语句,仅支持逻辑表达式、算术表达式、三元表达式和正则匹配。
- 不支持八进制数字字面量,仅支持十进制和十六进制数字字面量。
使用场景
- 规则判断以及规则引擎
- 公式计算
- 动态脚本控制
2.代码工程
实验目的
利用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 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
单个规则注解
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 public class 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) { 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; @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 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); } }
以上只是一些关键代码,所有代码请参见下面代码仓库
代码仓库
3.测试
- 启动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}
4.引用
https://github.com/killme2008/aviatorscript/blob/master/README-EN.md
以上就是SpringBoot集成Aviator实现参数校验的代码工程的详细内容,更多关于SpringBoot Aviator参数校验的资料请关注脚本之家其它相关文章!