Spring Boot集成Aviator实现参数校验

简介: Aviator是一个高性能、轻量级的Java表达式求值引擎,适用于动态表达式计算。其特点包括支持多种运算符、函数调用、正则匹配、自动类型转换及嵌套变量访问,性能优异且依赖小。适用于规则引擎、公式计算和动态脚本控制等场景。本文介绍了如何结合Aviator与AOP实现参数校验,并附有代码示例和仓库链接。

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

体验AI代码助手

代码解读

复制代码

<?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校验规则

kotlin

体验AI代码助手

代码解读

复制代码

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

单个规则注解

java

体验AI代码助手

代码解读

复制代码

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 "";

}

多个规则注解

java

体验AI代码助手

代码解读

复制代码

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拦截注解

ini

体验AI代码助手

代码解读

复制代码

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;
   }
}

全局异常拦截

scala

体验AI代码助手

代码解读

复制代码

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.测试


转载来源:https://juejin.cn/post/7429689783943495714

相关文章
|
2月前
|
数据可视化 Java BI
将 Spring 微服务与 BI 工具集成:最佳实践
本文探讨了 Spring 微服务与商业智能(BI)工具集成的潜力与实践。随着微服务架构和数据分析需求的增长,Spring Boot 和 Spring Cloud 提供了构建可扩展、弹性服务的框架,而 BI 工具则增强了数据可视化与实时分析能力。文章介绍了 Spring 微服务的核心概念、BI 工具在企业中的作用,并深入分析了两者集成带来的优势,如实时数据处理、个性化报告、数据聚合与安全保障。同时,文中还总结了集成过程中的最佳实践,包括事件驱动架构、集中配置管理、数据安全控制、模块化设计与持续优化策略,旨在帮助企业构建高效、智能的数据驱动系统。
187 1
将 Spring 微服务与 BI 工具集成:最佳实践
|
4月前
|
安全 Java 数据库
第16课:Spring Boot中集成 Shiro
第16课:Spring Boot中集成 Shiro
734 0
|
4月前
|
消息中间件 存储 Java
第15课: Spring Boot中集成ActiveMQ
第15课: Spring Boot中集成ActiveMQ
464 0
|
5月前
|
人工智能 Java 测试技术
Spring Boot 集成 JUnit 单元测试
本文介绍了在Spring Boot中使用JUnit 5进行单元测试的常用方法与技巧,包括添加依赖、编写测试类、使用@SpringBootTest参数、自动装配测试模块(如JSON、MVC、WebFlux、JDBC等),以及@MockBean和@SpyBean的应用。内容实用,适合Java开发者参考学习。
610 0
|
8月前
|
安全 Java Apache
微服务——SpringBoot使用归纳——Spring Boot中集成 Shiro——Shiro 身份和权限认证
本文介绍了 Apache Shiro 的身份认证与权限认证机制。在身份认证部分,分析了 Shiro 的认证流程,包括应用程序调用 `Subject.login(token)` 方法、SecurityManager 接管认证以及通过 Realm 进行具体的安全验证。权限认证部分阐述了权限(permission)、角色(role)和用户(user)三者的关系,其中用户可拥有多个角色,角色则对应不同的权限组合,例如普通用户仅能查看或添加信息,而管理员可执行所有操作。
438 0
|
8月前
|
安全 Java 数据安全/隐私保护
微服务——SpringBoot使用归纳——Spring Boot中集成 Shiro——Shiro 三大核心组件
本课程介绍如何在Spring Boot中集成Shiro框架,主要讲解Shiro的认证与授权功能。Shiro是一个简单易用的Java安全框架,用于认证、授权、加密和会话管理等。其核心组件包括Subject(认证主体)、SecurityManager(安全管理员)和Realm(域)。Subject负责身份认证,包含Principals(身份)和Credentials(凭证);SecurityManager是架构核心,协调内部组件运作;Realm则是连接Shiro与应用数据的桥梁,用于访问用户账户及权限信息。通过学习,您将掌握Shiro的基本原理及其在项目中的应用。
328 0
|
8月前
|
NoSQL Java 关系型数据库
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 介绍
本文介绍在 Spring Boot 中集成 Redis 的方法。Redis 是一种支持多种数据结构的非关系型数据库(NoSQL),具备高并发、高性能和灵活扩展的特点,适用于缓存、实时数据分析等场景。其数据以键值对形式存储,支持字符串、哈希、列表、集合等类型。通过将 Redis 与 Mysql 集群结合使用,可实现数据同步,提升系统稳定性。例如,在网站架构中优先从 Redis 获取数据,故障时回退至 Mysql,确保服务不中断。
337 0
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 介绍
|
2月前
|
监控 Cloud Native Java
Spring Integration 企业集成模式技术详解与实践指南
本文档全面介绍 Spring Integration 框架的核心概念、架构设计和实际应用。作为 Spring 生态系统中的企业集成解决方案,Spring Integration 基于著名的 Enterprise Integration Patterns(EIP)提供了轻量级的消息驱动架构。本文将深入探讨其消息通道、端点、过滤器、转换器等核心组件,以及如何构建可靠的企业集成解决方案。
247 0
|
4月前
|
Java 关系型数据库 数据库连接
Spring Boot项目集成MyBatis Plus操作PostgreSQL全解析
集成 Spring Boot、PostgreSQL 和 MyBatis Plus 的步骤与 MyBatis 类似,只不过在 MyBatis Plus 中提供了更多的便利功能,如自动生成 SQL、分页查询、Wrapper 查询等。
395 3
下一篇
oss云网关配置