Original link:www.ciphermagic.cn/spring-boot…

Updated out-of-the-box version:Github.com/ciphermagic…

This paper introduces an AOP based on Spring Boot and JDK8, combined with custom annotations to achieve universal interface parameter verification.

The reason

Currently, the common method of parameter verification is to add annotations to the entity class, but the verification rules are different for different methods. For example, there is an AccountVO entity:

public class AccountVO {
    private String name; / / name
    private Integer age; / / age
}
Copy the code

Suppose you have a business where a user needs to fill in their name and age when they register, and only need to fill in their name when they log in. It is not appropriate to apply validation rules to entity classes.

Therefore, I always want to achieve a method level parameter verification. For the same entity parameter, different methods can apply different verification rules, so this tool was born, and has been used in daily work for a long time.

introduce

Here’s how to use it:

@Service
public class TestImpl implements ITestService {

    @Override
    @Check({"name"."age"})
    public void testValid(AccountVO vo) {
        // ...}}Copy the code

The @check annotation on the method indicates that the name and age attributes in the AccountVO parameters cannot be null. In addition to non-null check, it also supports size judgment and equal check:

@Check({"id>=8"."name! =aaa"."title<10"})
Copy the code

The default error message returns the field, the reason for the error, and the method called, for example:

updateUserId must not null while calling testValid

id must >= 8 whilecalling testValid name must ! = aaawhile calling testValid
Copy the code

Custom error return messages are also supported:

@Check({"Title <=8: The title should be no more than 8 words, including punctuation"})
public void testValid(TestPO po) {
    // ...
}
Copy the code

To replace the default error information, add: to the verification rule and add custom information.

PS: The core principle is to obtain the value of the field in the parameter entity through reflection, and then verify it according to the rules. Therefore, only methods containing one parameter are supported at present, and the parameter cannot be the basic type.

use

How to use AOP in Spring-Boot is not described here, mainly introduces the core code in AOP.

Maven rely on

In addition to the spring-boot dependencies, the required third-party dependencies, not core dependencies, can be selected according to personal habits:

<! String validation -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.3.2 rainfall distribution on 10-12</version>
</dependency>

<! -- For log printing -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>
Copy the code

Custom annotations

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

* Created by cipher on 2017/9/20. */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RUNTIME)
public @interface Check {

    // Field verification rule format: field name + verification rule + colon + error message, for example :ID <10: The ID must be less than 10
    String[] value();

}
Copy the code

The core code

The interface method with the @check annotation is intercepted by the section. Before the method is executed, the parameter verification is performed. If there is an error message, the system returns:

@Around(value = "@com.cipher.checker.Check") // The path of the custom annotation is changed here
public Object check(ProceedingJoinPoint point) throws Throwable {
    Object obj;
    // Check parameters
    String msg = doCheck(point);
    if(! StringUtils.isEmpty(msg)) {// It is possible to return its own wrapped return class
        throw new IllegalArgumentException(msg);
    }
    obj = point.proceed();
    return obj;
}
Copy the code

In the doCheck method, the main principle is to obtain the field name and verification rule specified on the annotation, obtain the value of the corresponding field in the parameter entity through reflection, and then verify:

/** ** *@param point ProceedingJoinPoint
 * @returnError message */
private String doCheck(ProceedingJoinPoint point) {
    // Get method parameter values
    Object[] arguments = point.getArgs();
    // Get method
    Method method = getMethod(point);
    String methodInfo = StringUtils.isEmpty(method.getName()) ? "" : " while calling " + method.getName();
    String msg = "";
    if (isCheck(method, arguments)) {
        Check annotation = method.getAnnotation(Check.class);
        String[] fields = annotation.value();
        Object vo = arguments[0];
        if (vo == null) {
            msg = "param can not be null";
        } else {
            for (String field : fields) {
                // Parse the fields
                FieldInfo info = resolveField(field, methodInfo);
                // Get the value of the field
                Object value = ReflectionUtil.invokeGetter(vo, info.field);
                // Execute the verification ruleBoolean isValid = info.optEnum.fun.apply(value, info.operatorNum); msg = isValid ? msg : info.innerMsg; }}}return msg;
}
Copy the code

You can see that the main logic is:

Parse the field -> Get the value of the field -> Execute the validation rule

Internally maintains an enumeration class in which the relevant validation operations are specified:

/** * enumerates */
enum Operator {
    /** * is greater than */
    GREATER_THAN(">", CheckParamAspect::isGreaterThan),
    /** * is greater than or equal to */
    GREATER_THAN_EQUAL("> =", CheckParamAspect::isGreaterThanEqual),
    /**
     * 小于
     */
    LESS_THAN("<", CheckParamAspect::isLessThan),
    /** * is less than or equal to */
    LESS_THAN_EQUAL("< =", CheckParamAspect::isLessThanEqual),
    /** ** does not equal */
    NOT_EQUAL(! "" =", CheckParamAspect::isNotEqual),
    /** * is not null */
    NOT_NULL("not null", CheckParamAspect::isNotNull);

    private String value;
    private BiFunction<Object, String, Boolean> fun;

    Operator(String value, BiFunction<Object, String, Boolean> fun) {
        this.value = value;
        this.fun = fun; }}Copy the code

For reasons of space, I don’t want to go through all of the code here. If you are interested, you can go to cipherMagic /java-learn/sandbox/ Checker for all of the source code

TODO

  • Packaged as a stand-alone component as a Spring Boot Starter
  • Regular expression verification is supported

The last

Thank you for reading. If you like, please give a thumbs up on Github. If you have any questions or suggestions, please leave a comment below.