Five minutes to take you to understand how Java/Springmvc springboot implements interface data verification calmly and elegantly

5.minutes to take you to understand how Java/Springmvc springboot implements interface data verification calmly and elegantly

Of course, springboot can use @Validated to implement interface data verification. Many spring-boot-starter-webs on the Internet come with hibernate-validator. I use springboot 2.3.4. It seems that it doesn't come with the following dependencies:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
 

This article will share with you a little skill that is summarized in the usual development! Friends who have written Java programs at work know that the most mainstream way to use Java development services is to define a Controller layer interface through Spring MVC, and define the interface request or return parameters in a Java entity class, so that Spring MVC After receiving the Http request (POST/GET), it will automatically map the request message to a Java object. Such code is usually written like this:

@RestController public class OrderController {@Autowired private OrderService orderServiceImpl; @PostMapping("/createOrder") public CreateOrderBO validationTest(@Validated CreateOrderDTO createOrderDTO) {return orderServiceImpl.createOrder(createOrderDTO);}

} I believe you are not unfamiliar with code such as copy code, but in the subsequent logic implementation process, you will encounter such a problem: "How to achieve the validity check of the message object data value after receiving the request parameters?". Some students may also think that this is not a problem, because whether a specific parameter field is empty, whether the value is in the agreed range, whether the format is legal, etc., just check in the business code. For example, various if-else data verifications can be performed on the message format in the Service implementation class.

In terms of function, redundant if-else code is fine, but in terms of code elegance, verbose if-else code can be very bloated. The following content will introduce a practical method to deal with such problems. The details will be introduced from the following aspects:

Use @Validated annotations to achieve direct binding and verification of Controller interface layer data; expand constraint annotations to verify the data value range; more flexible object data legitimacy verification tool encapsulation; abnormal data legitimacy verification results are returned uniformly Processing; Controller interface layer data binding verification. In fact, the Bean data verification tool commonly used in Java development is "hibernate-validator", which is a hibernete independent jar package, so it is not necessary to use this jar package To integrate the Hibernete framework. The jar package mainly implements and extends the javax.validation (a Bean verification specification developed based on the JSR-303 standard) interface.

Since Spring Boot integrates "hibernate-validator" internally by default, Java projects built with Spring Boot can directly use the relevant solutions to implement Bean data verification. For example, the most commonly written Controller layer interface parameter object, you can directly write such code when defining the Bean class:

@Data public class CreateOrderDTO {@NotNull(message = "The order number cannot be empty") private String orderId; @NotNull(message = "The order amount cannot be empty") @Min(value = 1, message = "The order amount cannot be less than 0") private Integer amount; @Pattern(regexp = "^1[3|4|5|7|8][0-9]{9}$", message = "User mobile phone number is illegal") private String mobileNo ; private String orderType; private String status;

} Copy the code as shown in the code above, we can use the @NotNull annotation to constrain the field must not be empty, we can also use the @Min annotation to constrain the minimum value of the field, or we can also use the @Pattern annotation to use regular expressions The format of the constraint field (such as mobile phone number format) and so on.

The above annotations are provided by default in the "hibernate-validator" dependency package. There are many more commonly used annotations, for example:

Using these constraint annotations, we can easily get the interface data verification, without the need to write a large number of if-else in the business logic to verify the validity of the data. After defining the Bean parameter object and using the relevant solution to implement the parameter value constraints, you only need to use the @Validated annotation in the Controller layer interface definition to automatically perform data binding verification after receiving the parameters. The specific code is as follows:

@PostMapping("/createOrder") public CreateOrderBO validationTest(@Validated CreateOrderDTO createOrderDTO) {return orderServiceImpl.createOrder(createOrderDTO);} Copy the code as shown above, in the Controller layer, the @Validated annotation provided by Spring can automatically realize the data bean Binding verification, if the data is abnormal, a verification exception will be thrown uniformly!

Constraint annotations are extended in the "hibernate-validator" dependent jar package. Although a lot of convenient constraint annotations are provided, there are also cases that do not meet certain actual needs. For example, we want to agree on a value in a parameter. Enumeration range, such as orderType order type only allows two values of "pay" and "refund", then the existing constraint annotations may not be particularly applicable. In addition, for such enumeration values, we also want to directly match the enumeration definition in the code in the constraint definition to better unify the enumeration definition of interface parameters and business logic. In this case, we can also extend the definition by ourselves to restrict the annotation logic accordingly.

Next, we define a new constraint annotation @EnumValue to achieve the effect we mentioned above. The specific code is as follows:

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) @Documented @Constraint(validatedBy = {EnumValueValidator.class}) public @interface EnumValue {//default error message String message() default "Required Specify value";//Support string array verification String[] strValues() default {};//Support int array verification int[] intValues() default {};//Support enumeration list verification Class\[\] enumValues () default {};//Group Class[] groups() default {};//Load Class\[\] payload() default {};//Use when specifying multiple @Target({FIELD, METHOD, PARAMETER , ANNOTATION\_TYPE}) @Retention(RUNTIME) @Documented @interface List {EnumValue\[\] value();}/\*\*/* Validation class logic definition\*/class EnumValueValidator implements ConstraintValidator {//character String type array private String\[\] strValues;//int type array private int\[\] intValues;//enumeration class private Class[] enumValues;/** * Initialization method* * @param constraintAnnotation */@Override public void initialize(EnumValue constraintAnnotation) {strValues = constraintAnnotation.strValues(); intValues = constraintAnnotation.intValues(); enumValues = constraintAnnotation.enumValues();}/* * * Validation method * * @param value * @param context * @return */@SneakyThrows @Override public boolean isValid(Object value, ConstraintValidatorContext context) {//Check and match against string array if (strValues != null && strValues.length> 0) {if (value instanceof String) {for (String s: strValues) {//Determine whether the value type is an Integer type if (s.equals(value)) {return true;}}}}//Check and match against integer array if (intValues != null && intValues.length> 0) {if (value instanceof Integer) {//Determine whether the value type is Integer type for (Integer s: intValues) {if (s == value) {return true;}}}}//Check and match against enumeration type if (enumValues != null &&enumValues.length> 0) {for (Class<?> cl: enumValues) {if (cl.isEnum()) {//Enumeration class verification Object[] objs = cl.getEnumConstants();//You need to pay attention here, When defining an enumeration, the enumeration value name is uniformly represented by value Method method = cl.getMethod("getValue"); for (Object obj: objs) {Object code = method.invoke(obj, null); if (value.equals (code.toString())) {return true;}}}}} return false;}}

} Copy code The @EnumValue constraint annotation shown above is a very practical extension. Through this annotation, we can achieve constraints on the parameter value range (not the size range), and it supports three data types: int, string, and enum The specific usage is as follows:

/**

  • Customized annotations, support parameter values to match the specified type array list values (the disadvantage is that enumeration values need to be written in the annotations of the field definition)/@EnumValue(strValues = {"pay", "refund"}, message = "Order type error") private String orderType;/*

  • Customized annotations to achieve automatic matching and verification of parameter values and enumeration lists (which can better match actual business development) */@EnumValue(enumValues = Status.class, message = "Status value is not in the specified range") private String status; Copy the code as shown in the code above. The extended annotation can either use the strValues or intValues property to programmatically enumerate the value range, or directly bind the enumeration definition through enumValues. However, it should be noted that for general considerations, the names of attributes defined by specific enumerations must be uniformly matched to value and desc. For example, the Status enumeration is defined as follows:

public enum Status {PROCESSING(1, "Processing"), SUCCESS(2, "Order has been completed"); Integer value; String desc; Status(Integer value, String desc) {this.value = value; this.desc = desc;} public Integer getValue() {return value;} public String getDesc() {return desc;}

} Copy the code and expand through annotations to achieve more convenient constrained annotations!

More flexible data validation tool class encapsulation In addition to using @Validated directly in the Controller layer to perform binding data validation, in some cases, for example, a field in your parameter object is a composite object, or a certain field in the business layer The input object defined by this method also needs to be verified for data legitimacy. In this case, how to achieve the same verification effect as the Controller layer?

It needs to be explained that in this case @Validated can no longer be used directly, because the @Validated annotation plays a role mainly because Spring MVC implements automatic data binding verification in the process of receiving parameters, while in ordinary business methods or composite parameter objects There is no way to bind verification directly. In this case, we can achieve the same verification effect by defining the ValidateUtils tool class. The specific code is as follows:

public class ValidatorUtils {private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();/** * The bean's overall verification, if it is not in compliance with the specification, throws the first violation exception*/public static void validate(Object obj, Class <?>... groups) {Set<ConstraintViolation> resultSet = validator.validate(obj, groups); if (resultSet.size()> 0) {//If there is an error result, it will be parsed and pieced together Exception throws List errorMessageList = resultSet.stream().map(o -> o.getMessage()).collect(Collectors.toList()); StringBuilder errorMessage = new StringBuilder(); errorMessageList.stream().forEach(o -> errorMessage.append(o + ";")); throw new IllegalArgumentException(errorMessage.toString());}}

} Copy the code as shown above, we define a tool class implementation based on the "javax.validation" interface, so that we can implement constraint annotations on the Bean object through the validation tool class in scenarios where @Validated is not directly bound to the verification. The verification processing, the specific code used is as follows:

public boolean orderCheck(OrderCheckBO orderCheckBO) {//Perform data validation on the parameter object ValidatorUtils.validate(orderCheckBO); return true;} Copy the code and the method input object can still continue to use the binding annotations we introduced earlier for agreement, For example, the input object of the above method is defined as follows:

@Data @Builder public class OrderCheckBO {@NotNull(message = "The order number cannot be empty") private String orderId; @Min(value = 1, message = "The order amount cannot be less than 0") private Integer orderAmount; @NotNull(message = "The creator cannot be empty") private String operator; @NotNull(message = "The operation time cannot be empty") private String operatorTime;

} Copy the code so that the overall programming experience can be consistent!

Unified processing of abnormal data legality verification results. Through the various constraint annotations we talked about earlier, we have implemented a unified data verification of the Controller layer interface and business method parameter objects. In order to maintain the unified processing of verification exception handling and unified output of error messages, we can also define a general exception handling mechanism to ensure that various data verification errors can be fed back to the caller in a unified error format. The specific code is as follows:

@Slf4j @ControllerAdvice public class GlobalExceptionHandler {/** * Unified handling of parameter verification error exceptions (non-Spring interface data binding verification) * * @param response * @param e * @return */@ExceptionHandler(BindException.class) @ ResponseBody public ResponseResult processValidException(HttpServletResponse response, BindException e) {response.setStatus(HttpStatus.INTERNAL\_SERVER\_ERROR.value());//Get verification error result information, and assemble the information List errorStringList = e.getBindingResult() .getAllErrors() .stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList()); String errorMessage = String.join("; ", errorStringList); response.setContentType("application/json;charset =UTF-8 ); log.error(e.toString() +/_ + e.getMessage(), e); return ResponseResult.systemException(GlobalCodeEnum.GL\_FAIL\_9998.getCode(), errorMessage) ;}/\*\*/* Unified handling of parameter validation error exceptions\*/* @param response/* @param e/* @return/*/@ExceptionHandler(IllegalArgumentException.class) @ResponseBody public ResponseResult processValidException(HttpServletResponse response, IllegalArgumentException e) {response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); String errorMessage = String.join("; ", e.getMessage()); response.setContentType("application/json;charset=UTF-8" ); log.error(e.toString() + "_" + e.getMessage(), e); return ResponseResult.systemException(GlobalCodeEnum.GL_FAIL_9998.getCode(), errorMessage);} ...join("; ", e.getMessage()); response.setContentType("application/json;charset=UTF-8"); log.error(e.toString() + "_" + e.getMessage(), e); return ResponseResult.systemException(GlobalCodeEnum.GL_FAIL_9998.getCode(), errorMessage);} ...join("; ", e.getMessage()); response.setContentType("application/json;charset=UTF-8"); log.error(e.toString() + "_" + e.getMessage(), e); return ResponseResult.systemException(GlobalCodeEnum.GL_FAIL_9998.getCode(), errorMessage);} ...

} Copy the code as shown above, we define a unified exception handling mechanism for the previous two data verification methods, so that the error information of the data verification can be fed back to the caller through a unified message format, thereby realizing the interface data message The unified return!

The code of the general interface parameter object ResponseResult is defined as follows:

@Data @Builder @NoArgsConstructor @AllArgsConstructor @JsonPropertyOrder({"code", "message", "data"}) public class ResponseResult implements Serializable {private static final long serialVersionUID = 1L;/** * Returned object*/@JsonInclude (JsonInclude.Include.NON_NULL) private T data;/** * Returned code*/private Integer code;/** * Returned information*/private String message;/** * @param data Returned data* @param Returned data type* @return response result*/public static ResponseResult OK(T data) {return packageObject(data, GlobalCodeEnum.GL_SUCC_0);}/** * Custom system exception information* * @param code * @param message self Define message * @param * @return */public static ResponseResult systemException(Integer code, String message) {return packageObject(null, code, message);}

} Copy the code Of course, such a unified message format not only handles abnormal returns, the normal data message format can also be unified encapsulated by this object!

The content of this article demonstrates to you from a practical perspective, how to write general data verification logic in daily work, I hope it can be helpful to everyone!

Written at the end, welcome everyone to pay attention to my public account [The wind and waves are as quiet as the code], a large number of Java-related articles, learning materials will be updated in it, and the collated materials will also be placed in it.

If you think the writing is good, just like it and add a follower! Pay attention, don t get lost, keep updating! ! !

Author: Thai Chicken Valley Links: juejin.cn/post/690376... Source: Nuggets copyright reserved by the authors. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.