Write custom parameter validation

This article has been published for more than two months since I left my previous company two months ago (the reason may be at the end of the year, this article is ignored for the moment). When I came to the Jingdong Group where I am now, I need to take time to familiarize myself with the environment and settle something new, so I am not so diligent in writing this article, I have to say this is an opportunity and also for myself.An important decision in your career.

Say that the main share of this content is the validation of custom method parameters. The basic validation of parameters is often seen in external interfaces or public methods. Friends who have used hibernate's validation method must not be unfamiliar. Reading this content can help friends to have a good understanding of the validation of custom parameters:

  • Ideas for Custom Parameter Validation
  • Common methods for validating operational parameters
  • aop method parameter validation example

Ideas for Custom Parameter Validation

For custom parameter validation, there are several steps to be aware of:

  1. How to distinguish between the parameters that need to be validated, or the attributes that need to be validated in the parameter entity class (Answer: Annotation tags are available)
  2. For parameters, verify which data formats (e.g., non-empty, mailbox, phone, regular, etc.)
  3. How to get the parameter data to validate (for example, how to get the data passed in by the method parameter entity)
  4. Description of error message prompted when validation fails (e.g., uniform default validation error information, or exposure of error message text passed from tag validation comment)
  5. At which step is the check done (for example, when entering a method, or when the location can be uniformly checked using aop)

Common methods for validating operational parameters

Based on the ideas described above, we first need annotations to mark which entity attributes need to be checked differently, so here are two check annotations (for brevity in this chapter): IsNotBlank (check cannot be empty) and RegExp (regular match check), which are coded as follows:

1 @Documented
2 @Retention(RetentionPolicy.RUNTIME)
3 @Target(ElementType.FIELD)
4 public @interface IsNotBlank {
5     String des() default "";
6 }
1 @Documented
2 @Retention(RetentionPolicy.RUNTIME)
3 @Target(ElementType.FIELD)
4 public @interface RegExp {
5     String pattern();
6 
7     String des() default "";
8 }

Then, in order to unify the common validation methods created here, this method needs to pass specific instances of the parameters to be validated, the main work of which is:

  1. Get the properties of the parameter entity by passing in the parameter
  2. Setting field.setAccessible(true) allows you to get data from the corresponding properties
  3. Validate the captured data format according to the corresponding tag attribute annotations, and the failure of format validation directly prompts the des description

Here are some common validation methods:

 1 public class ValidateUtils {
 2 
 3     public static void validate(Object object) throws IllegalAccessException {
 4         if (object == null) {
 5             throw new NullPointerException("Data Format Check Object cannot be empty");
 6         }
 7         //Get Property Columns
 8         Field[] fields = object.getClass().getDeclaredFields();
 9         for (Field field : fields) {
10             //Filter attributes without validation annotations
11             if (field.getAnnotations() == null || field.getAnnotations().length <= 0) {
12                 continue;
13             }
14             //allow private Property is accessed
15             field.setAccessible(true);
16             Object val = field.get(object);
17             String strVal = String.valueOf(val);
18 
19             //Specific Validation
20             validField(field, strVal);
21         }
22     }
23 
24     /**
25      * Specific Validation
26      *
27      * @param field  Attribute Column
28      * @param strVal Attribute Value
29      */
30     private static void validField(Field field, String strVal) {
31         if (field.isAnnotationPresent(IsNotBlank.class)) {
32             validIsNotBlank(field, strVal);
33         }
34         if (field.isAnnotationPresent(RegExp.class)) {
35             validRegExp(field, strVal);
36         }
37         /** add... **/
38     }
39 
40     /**
41      * Matching Regular
42      *
43      * @param field
44      * @param strVal
45      */
46     private static void validRegExp(Field field, String strVal) {
47         RegExp regExp = field.getAnnotation(RegExp.class);
48         if (Strings.isNotBlank(regExp.pattern())) {
49             if (Pattern.matches(regExp.pattern(), strVal)) {
50                 return;
51             }
52             String des = regExp.des();
53             if (Strings.isBlank(des)) {
54                 des = field.getName() + "Incorrect format";
55             }
56             throw new IllegalArgumentException(des);
57         }
58     }
59 
60     /**
61      * Non-empty judgment
62      *
63      * @param field
64      * @param val
65      */
66     private static void validIsNotBlank(Field field, String val) {
67         IsNotBlank isNotBlank = field.getAnnotation(IsNotBlank.class);
68         if (val == null || Strings.isBlank(val)) {
69             String des = isNotBlank.des();
70             if (Strings.isBlank(des)) {
71                 des = field.getName() + "Cannot be empty";
72             }
73             throw new IllegalArgumentException(des);
74         }
75     }
76 }

With specific validation methods, we need a test instance, such as the following test interfaces and entities:

1 public class TestRq extends BaseRq implements Serializable {
2 
3     @IsNotBlank(des = "Nickname cannot be empty")
4     private String nickName;
5     @RegExp(pattern = "\\d{10,20}", des = "Number must be a number")
6     private String number;
7     private String des;
8     private String remark;
9 }
1     @PostMapping("/send")
2     public BaseRp<TestRp> send(@RequestBody TestRq rq) throws IllegalAccessException {
3         ValidateUtils.validate(rq);
4         return testService.sendTestMsg(rq);
5     }

aop method parameter validation example

The above is written around a common validation method, which is usually combined with aop in real-world scenarios; two annotations are customized, the MethodValid method annotation (whether to validate all parameters) and the ParamValid Parameter annotation (a parameter on the tagging method):

 1 @Documented
 2 @Retention(RetentionPolicy.RUNTIME)
 3 @Target(value = {ElementType.METHOD})
 4 public @interface MethodValid {
 5     /**
 6      * Verify all parameters
 7      *
 8      * @return true
 9      */
10     boolean isValidParams() default true;
11 }
1 @Documented
2 @Retention(RetentionPolicy.RUNTIME)
3 @Target(value = {ElementType.PARAMETER})
4 public @interface ParamValid {
5 }

With two tag annotations to create aop, I'm here based on an example of the springboot framework, all introducing the following mvn:

1         <dependency>
2             <groupId>org.springframework.boot</groupId>
3             <artifactId>spring-boot-starter-aop</artifactId>
4         </dependency>

aop then needs to do the following logic:

  1. Get method pass parameters (param1,param2...)
  2. Traverse through each parameter entity and verify if there are validation notes
  3. Traverse parameters marked with ParamValid comment, check if there are validation comments

The special thing here is that to get annotations for method parameters, you need method.getParameterAnnotations() to get all the parameter annotations, and then use the index to get the annotations for the parameters; the following aop code:

 1 package com.shenniu003.common.validates;
 2 
 3 import com.shenniu003.common.validates.annotation.MethodValid;
 4 import com.shenniu003.common.validates.annotation.ParamValid;
 5 import org.aspectj.lang.ProceedingJoinPoint;
 6 import org.aspectj.lang.annotation.Around;
 7 import org.aspectj.lang.annotation.Aspect;
 8 import org.aspectj.lang.reflect.MethodSignature;
 9 import org.springframework.stereotype.Component;
10 
11 import java.lang.annotation.Annotation;
12 import java.lang.reflect.Method;
13 import java.util.Arrays;
14 
15 /**
16  * des: 
17  *
18  * @author: shenniu003
19  * @date: 2019/12/01 11:04
20  */
21 @Aspect
22 @Component
23 public class ParamAspect {
24 
25     @Around(value = "@annotation(methodValid)", argNames = "joinPoint,methodValid")
26     public Object validMethod(ProceedingJoinPoint joinPoint, MethodValid methodValid) throws Throwable {
27         MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
28         Method method = methodSignature.getMethod();
29         System.out.println("method:" + method.getName());
30         String strArgs = Arrays.toString(joinPoint.getArgs());
31         System.out.println("params:" + strArgs);
32 
33         //Get annotations for all parameters of the method
34         Annotation[][] parametersAnnotations = method.getParameterAnnotations();
35 
36         for (int i = 0; i < joinPoint.getArgs().length; i++) {
37             Object arg = joinPoint.getArgs()[i];
38             if (arg == null) {
39                 continue; //
40             }
41 
42             if (methodValid.isValidParams()) {
43                 //Verify all parameters
44                 System.out.println(arg.getClass().getName() + ":" + arg.toString());
45                 ValidateUtils.validate(arg);
46             } else {
47                 //Validate parameters only with ParamValid Annotated parameters
48                 //Get all comments for the current parameter
49                 Annotation[] parameterAnnotations = parametersAnnotations[i];
50                 //Match parameter check notes
51                 if (matchParamAnnotation(parameterAnnotations)) {
52                     System.out.println(Arrays.toString(parameterAnnotations) + " " + arg.getClass().getName() + ":" + arg.toString());
53                     ValidateUtils.validate(arg);
54                 }
55             }
56         }
57         return joinPoint.proceed();
58     }
59 
60     /**
61      * Notes on whether to match parameters
62      *
63      * @param parameterAnnotations All comments corresponding to parameters
64      * @return Whether to include target annotations
65      */
66     private boolean matchParamAnnotation(Annotation[] parameterAnnotations) {
67         boolean isMatch = false;
68         for (Annotation parameterAnnotation : parameterAnnotations) {
69             if (ParamValid.class == parameterAnnotation.annotationType()) {
70                 isMatch = true;
71                 break;
72             }
73         }
74         return isMatch;
75     }
76 }

Here we write a test case in three ways, validating all parameters of the method, validating no parameters without validation, validating the parameters of the method with @ParamValid to achieve a different way of validating the required parameters:

 1     //Validation Method All Parameters
 2     @MethodValid
 3     public void x(TestRq param1, String param2) {
 4     }
 5     //No parameter no validation
 6     @MethodValid
 7     public void xx() {
 8     }
 9     //Validation method parameters with@ParamValid Parameters
10     @MethodValid(isValidParams = false)
11     public void xxx(TestRq param1, @ParamValid String param2) {
12     }

Also use the send interface as the test entry, calling the three methods above:

1     @PostMapping("/send")
2     @MethodValid
3     public BaseRp<TestRp> send(@RequestBody TestRq rq) throws IllegalAccessException {
4 //        ValidateUtils.validate(rq);
5         testController.x(rq, "Validation Method All Parameters");
6         testController.xx();
7         testController.xxx(rq, "Validation method parameters with@ParamValid Parameters");
8         return testService.sendTestMsg(rq);
9     }

Tags: Java Attribute Hibernate SpringBoot

Posted on Mon, 02 Dec 2019 16:54:40 -0500 by brem13