Spring AOP+ reflection implements custom dynamic configuration verification rules, making the verification rules fly

Original author: Minuor R
Original address: http://www.minuor.com/1524369999/article
Reprint statement: Please indicate the original address for reprinting, and pay attention to copyright protection, thank you!

Scene Subtotal

Previous projects used hibernate-validator to verify parameters, but in fact there will be some small problems, that is, the verification rules are all done through annotations, so if the project is online, the parameter verification rules cannot be modified. , if there is a problem with the verification rules, you must modify it and then go online again in an emergency (this problem occurred before because of the mobile phone number format verification, because the new number segment is not supported). In order to adapt to the dynamic configuration verification rules, we no longer use the hibernate-validator verification rules in new projects, but write a small function to achieve it.

Realize ideas

1. To realize this dynamic configuration, it is necessary to be able to modify the rules at any time and apply them to the actual business logic. It is impossible to write directly in the code. Therefore, it is a good choice to use database records here;
2. It is necessary to The parameter verification entered by the controller cannot add calling logic to each method. This must write a public method and use Spring AOP to cut into all controller methods;
3. The service request method, using this method is the most convenient It is to use the post request. After entering the parameters, the parameters are encapsulated in a class, get the class, use reflection, and take out the parameter name and parameter value of the parameter.
Basically the above idea, cut into all methods in the controller class, get the request Dto class, use reflection technology to take out all parameter names and parameter values, obtain the verification rules of all parameters under the current Dto class from the database, and then check the parameters in turn. Check it out.

project build

Project structure

Project structure
Aspect: Aspect (DynamicCheckAspect) and a verification engine (DynamicCheckEngine), the fields are reflected in the aspect, the verification rules are queried, and then the fields are handed over to the verification engine to complete the verification action;
controller: interface entry, DynamicCheckController provides verification tests;
dao: There are two directories under dao, mapper and model, which are used to store Mapper interface classes and query result data encapsulation classes;
dto: request parameter encapsulation class (DynamicCheckReqDto), response parameter encapsulation class (DynamicCheckRespDto);
exception: custom exception class Storage location;
service: business logic code;
ApplicationStart: Spring Boot startup entry;
resource: store mapper.xml file and application.properties configuration and log configuration logback.xml.

Database preparation

The database needs to build three tables, the verification template table (t_template_info), the verification template rule table (t_template_rule_info), the entity rule association table (t_bean_rule_info), only the basic fields of the table, need SQL, you can go to the code cloud or git. The code, the datasql.sql file in the project is very detailed, and also contains the initial data.

t_template_info:

template_idvarchar(16) NOT NULL COMMENT 'template number',
template_descvarchar(64) DEFAULT NULL COMMENT 'template description',
template_statustinyint(4) NOT NULL DEFAULT '1' COMMENT 'template status (0: not used, 1: used)',
check_levelint(11) NOT NULL COMMENT 'Check priority'

t_template_rule_info:

rule_idvarchar(16) NOT NULL COMMENT 'rule number',
template_idvarchar(16) NOT NULL COMMENT 'template number',
rule_expressvarchar(128) NOT NULL COMMENT 'rule expression',
toast_msgvarchar(128) NOT NULL COMMENT 'prompt information',
rule_statustinyint (4) NOT NULL DEFAULT '1' COMMENT 'rule status'

t_bean_rule_info:

bean_idvarchar(32) NOT NULL COMMENT 'entity class number',
rule_idvarchar(16) NOT NULL COMMENT 'rule number',
field_namevarchar(32) NOT NULL COMMENT 'field name',
field_descvarchar(128) DEFAULT NULL COMMENT 'field description',
check_statustinyint (4) DEFAULT '1' COMMENT 'Whether to verify'

Getting started code

pom.xml configuration
<!-- 统一制定spring boot版本 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.6.RELEASE</version>
</parent>

<!-- 版本配置信息 -->
<properties>
    <java.version>1.8</java.version>
    <lombok.version>1.16.10</lombok.version>
    <druid.version>1.1.0</druid.version>
    <mybatis.version>1.3.0</mybatis.version>
    <mysql.version>5.1.35</mysql.version>
    <commons-lang3.version>3.5</commons-lang3.version>
</properties>

<!-- 所需依赖 -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 日志 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
    </dependency>
    <!-- 数据库连接池 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>${druid.version}</version>
    </dependency>
    <!-- spring AOP包含aspectj等依赖,不需要单独引入 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <!-- spring+mybatis整合依赖 -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>${mybatis.version}</version>
    </dependency>
    <!-- mysql驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.version}</version>
    </dependency>
    <!-- lombok注解(注意在这里使用需要在idea上安装lombok插件) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <scope>provided</scope>
    </dependency>
    <!-- 工具类 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>${commons-lang3.version}</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <!-- maven编译插件 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <source>${java.version}</source>
                <target>${java.version}</target>
                <encoding>UTF-8</encoding>
            </configuration>
        </plugin>
    </plugins>
</build>
DynamicCheckAspect core code
@Component
@Slf4j
@Aspect
public class DynamicCheckAspect {

    @Autowired
    private DynamicCheckRuleService dynamicCheckRuleService;
    @Autowired
    private DynamicCheckEngine paramCheckEngine;

    /**
     * 定义切点
     */
    @Pointcut("execution(* com.minuor.dynamic.check.controller.*.*(..))")
    public void pointcut() {
    }

    /**
     * 定义环切
     */
    @Around("pointcut()")
    public void check(ProceedingJoinPoint joinPoint) {
        try {
            // 查询获取请求参数封装类(dto)的类名
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Class<?>[] parameterTypes = signature.getMethod().getParameterTypes();
            String beanName = null;
            if (parameterTypes != null && parameterTypes.length > 0) {
                beanName = parameterTypes[0].getSimpleName();
            }
            //查询当前beanName下字段的所有校验规则
            List<DynamicCheckRuleModel> modelList = null;
            if (StringUtils.isNotBlank(beanName)) {
                modelList = dynamicCheckRuleService.queryRuleByBeanName(beanName);
            }
            if (modelList != null && !modelList.isEmpty()) {
                //规则分类(根据字段名分类)
                Map<String, List<DynamicCheckRuleModel>> ruleMap = new HashMap<>();
                for (DynamicCheckRuleModel ruleModel : modelList) {
                    List<DynamicCheckRuleModel> fieldRules = ruleMap.get(ruleModel.getFieldName());
                    if (fieldRules == null) fieldRules = new ArrayList<>();
                    fieldRules.add(ruleModel);
                    ruleMap.put(ruleModel.getFieldName(), fieldRules);
                }
                //获取请求参数
                Object[] args = joinPoint.getArgs();
                if (args != null && args.length > 0) {
                    Object reqDto = args[0];
                    Field[] fields = reqDto.getClass().getDeclaredFields();
                    if (fields != null && fields.length > 0) {
                        for (Field field : fields) {
                            String fieldName = field.getName();
                            boolean isCheck = ruleMap.containsKey(fieldName);
                            if (!isCheck) continue;
                            field.setAccessible(true);
                            List<DynamicCheckRuleModel> paramRules = ruleMap.get(fieldName);
                            for (DynamicCheckRuleModel ruleModel : ruleMap.get(fieldName)) {
                                ruleModel.setFieldValue(field.get(reqDto));
                            }
                            //校验
                            paramCheckEngine.checkParamter(paramRules);
                        }
                    }
                }
            }
            joinPoint.proceed();
        } catch (Exception e) {
            throw new DynamicCheckException(e.getMessage());
        } catch (Throwable throwable) {
            throw new DynamicCheckException(throwable.getMessage());
        }
    }
}

The first thing here is to get the name of the Dto, and then go to the database to query the list of validation rules. If not, there is no need for validation, and the middle validation logic does not need to go any further.

DynamicCheckEngine core code
@Slf4j
@Component
public class DynamicCheckEngine {

    /**
     * 综合校验分发器
     *
     * @param paramRules
     */
    public void checkParamter(List<DynamicCheckRuleModel> paramRules) throws Exception {
        paramRules.sort(Comparator.comparing(DynamicCheckRuleModel::getCheckLevel));
        for (DynamicCheckRuleModel ruleModel : paramRules) {
            Method method = this.getClass().getMethod(ruleModel.getTemplateId(), DynamicCheckRuleModel.class);
            Object result = method.invoke(this, ruleModel);
            if (result != null) {
                throw new DynamicCheckException((String) result);
            }
        }
    }

    /**
     * 检查非空
     * 模板编号:notBlank
     */
    public String notBlank(DynamicCheckRuleModel roleModel) throws DynamicCheckException {
        Object fieldValue = roleModel.getFieldValue();
        if (fieldValue == null) {
            return generateToastMsg(roleModel);
        } else {
            if ((fieldValue instanceof String) && StringUtils.isBlank((String) fieldValue)) {
                return generateToastMsg(roleModel);
            }
        }
        return null;
    }

    /**
     * 检查非空
     * 模板编号:notNull
     */
    public String notNull(DynamicCheckRuleModel roleModel) throws DynamicCheckException {
        if (roleModel.getFieldValue() == null) return generateToastMsg(roleModel);
        return null;
    }

    /**
     * 检查长度最大值
     * 模板编号:lengthMax
     */
    public String lengthMax(DynamicCheckRuleModel roleModel) throws DynamicCheckException {
        String fieldValue = (String) roleModel.getFieldValue();
        if (fieldValue.length() > Integer.valueOf(roleModel.getRuleExpress().trim())) {
            return generateToastMsg(roleModel);
        }
        return null;
    }

    /**
     * 检查长度最小值
     * 模板编号:lengthMin
     */
    public String lengthMin(DynamicCheckRuleModel roleModel) throws DynamicCheckException {
        String fieldValue = (String) roleModel.getFieldValue();
        if (fieldValue.length() < Integer.valueOf(roleModel.getRuleExpress().trim())) {
            return generateToastMsg(roleModel);
        }
        return null;
    }

    /**
     * 检查值最大值
     * 模板编号:valueMax
     */
    public String valueMax(DynamicCheckRuleModel roleModel) throws DynamicCheckException {
        Double fieldValue = Double.valueOf(roleModel.getFieldValue().toString());
        if (fieldValue > Double.valueOf(roleModel.getRuleExpress())) {
            return generateToastMsg(roleModel);
        }
        return null;
    }

    /**
     * 检查值最小值
     * 模板编号:valueMin
     */
    public String valueMin(DynamicCheckRuleModel roleModel) throws DynamicCheckException {
        Double fieldValue = Double.valueOf(roleModel.getFieldValue().toString());
        if (fieldValue < Double.valueOf(roleModel.getRuleExpress())) {
            return generateToastMsg(roleModel);
        }
        return null;
    }

    /**
     * 正则格式校验
     * 模板编号:regex
     */
    public String regex(DynamicCheckRuleModel roleModel) throws DynamicCheckException {
        String value = (String) roleModel.getFieldValue();
        if (!Pattern.matches(roleModel.getRuleExpress(), value)) {
            return generateToastMsg(roleModel);
        }
        return null;
    }

    /**
     * 构建结果信息
     */
    private String generateToastMsg(DynamicCheckRuleModel roleModel) throws DynamicCheckException {
        String[] element = new String[]{StringUtils.isNotBlank(roleModel.getFieldDesc())
                ? roleModel.getFieldDesc() : roleModel.getFieldName(), roleModel.getRuleExpress()};
        String toast = roleModel.getToastMsg();
        int index = 0;
        while (index < element.length) {
            String replace = toast.replace("{" + index + "}", element[index] + "");
            if (toast.equals(replace)) break;
            toast = replace;
            index++;
        }
        return toast;
    }
}

In the verification method checkParameter, instead of going to if else to get the name of the verification template, the method is executed by reflection. Of course, the method name of the verification executed here should be the same as the template name. For example, if the verification is not empty, the template The name is notBlank, then the corresponding test method name is notBlank.

Summarize

1. All the codes in the project are not listed here. It feels unnecessary and redundant. The main ideas and core codes are enough. The download links on git and code cloud will be provided below the other codes;
2. Check and check here . Based on the post request, if there must be a get request in your project, then you need to re-plan how to define this validation rule. For example, get uses the method name, and post uses the Dto name;
3. This code is displayed as a demo, remember to use the Optimizing your own projects;
4. The exceptions checked here are all thrown out, and the exceptions are actually not thrown to the user. You can do unified filtering and encapsulation of exceptions in the controller.

project code

gitHub:https://gitee.com/minuor/dynamic-check
码云:https://gitee.com/minuor/dynamic-check

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324692754&siteId=291194637