Spring Validation

1. Data verification based on method level

Check (Normal check + block check)

forserviceLayer for parameter verification

@Data
public class UserInfoBo {
 
    @notblank (message = "user name cannot be blank ")
    private String username;

    /** * group check (specify specific group groups) */
    @notblank (message = "password cannot be blank ", groups = selectService.class)
    private String password;

    @notnull (message = "gender cannot be empty ", groups = SaveService. Class)
    private Byte gender;

    @notblank (message = "mailbox cannot be empty ", groups = SaveService. Class)
    @email (message = "mailbox format cannot be empty ", groups = SaveService. Class)
    private String email;

    private String phone;
    
    / * * *@ValidPerform cascading verification To verify attributes */ to be verified in internal objects
    @Valid
    private List<Book> bookList;
    
    public static class Book {
        @notblank (message = "title cannot be blank ")
        privateString bookName; }}Copy the code

You can add it to a defined interface@Validated

  • @ “Validated” : Applies to classes (interfaces), methods, and method parameters. But cannot be used for member attributes (fields), support grouping
  • Valid: Valid for methods, constructors, method parameters, and member attributes (field). Grouping is not supported. Cascading verification is supported
@Validated
public interface IUserService {

    @Validated(value = SaveService.class)
    void testOne(@Valid UserInfoBo userInfo);

    @Validated(value = SelectService.class)
    void testTwo(@Valid UserInfoBo userInfo);

    void testThree(@notblank (message = "user name cannot be blank ") String username);
}
Copy the code

Problems encountered

[Note 1] :

When verifying the constraints on method inputs, if@OverrideMethod of the parent class/interface, then this entry constraint can only be written on the parent class/interface, otherwise the following exception will be raised:

A method that overrides another method must not redefine the configuration of parameter constraints

Unless the constraints written by the class are exactly the same as those defined by the interface, they can be checked properly

[Note 2] :

Group verification If not only the group’s specific verification but also the default verification is required, the interface of the group needs to inherit:javax.validation.groups.Default

public interface SaveService extends Default {}Copy the code

[Note 3] :

Cyclic dependency problem: If A and B have cyclic dependencies and A has an @ “Validated” annotation, add @Lazy when injecting A into B to solve the problem that A is referenced repeatedly and the version of A is not the same

It is recommended to use@LazyAnnotation way to solve:

@Service
@Slf4j
public class UserServiceImpl implements IUserService {

    @Autowired
    @Lazy
    private IBookService bookService;

    @Override
    public void testOne(UserInfoBo userInfo) {
        log.info("Test 1:"+ JSONUtil.toJsonStr(userInfo)); }}Copy the code

2. Data verification based on MVC

Parameter verification for the Controller layer(Including ordinary checksum and packet checksum, skipped)

The controller layer code

@RestController
@RequestMapping(value = "app")
public class AppIndexController {

    @Autowired
    private AppIndexService appIndexService;

    @PostMapping(value = "/searchApps")
    @apiOperation (value = "Query my personal application ", notes =" Query my spatial application list by application name ")
    @ResponseBody
    public Mono<Result<PageResult<List<DcAppIndexVO>>>> searchApps(@Validated @RequestBody PageParam<AppIndexParam> pageParam) {
        returnMono.just(Result.buildSuccessResult(appIndexService.searchApps(pageParam))); }}Copy the code

Check the object

@Data
public class PageParam<T extends BaseDto> {

    @NotNull
    @Min(1)
    private Integer page = 1;

    @NotNull
    @Min(1)
    private Integer size = 20;

    @Valid
    private T data;

    public Integer getOffset(a) {
        return (page - 1) * size; }}Copy the code

Obviously, it can be found that, unlike the method level verification of the Service layer, if the parameter passed in the Controller layer needs to be verified, the annotation @validated / @valid can be directly added to the parameter of the method. The reason for this is that the MVC framework has introduced Spring Validation internally, which implicitly validates the input object of a method before calling it. The underlying function is still provided by the hibernate-Validation package. Let’s take a look at where and how it’s implemented in the MVC life cycle.

The DispatcherServlet uses HandlerMapping to match the URL path to the controller method that handles the URL. The call is a HandlerExecutionChain. The HandlerExecutionChain class is structured as follows:

In complete after interceptors, handler will go to call the real method, it is actually a HandlerAdaptor RequestMappingHandlerAdapter, for example, before a method is called, invokes the parser to parse Request data, Convert to the desired data type, and then validate the parameter object. Using the @requestBody annotation as an example, the parsing tool class performs the following process

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
    @Override
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

        parameter = parameter.nestedIfOptional();
        // Encapsulate the request data into a DTO object
        Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
        String name = Conventions.getVariableNameForParameter(parameter);

        if(binderFactory ! =null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
            if(arg ! =null) {
                // Perform data verification
                validateIfApplicable(binder, parameter);
                if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                    throw newMethodArgumentNotValidException(parameter, binder.getBindingResult()); }}if(mavContainer ! =null) { mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); }}returnadaptArgumentIfNecessary(arg, parameter); }}Copy the code
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
  Annotation[] annotations = parameter.getParameterAnnotations();
  for (Annotation ann : annotations) {
   Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
   if(validatedAnn ! =null || ann.annotationType().getSimpleName().startsWith("Valid")) { Object hints = (validatedAnn ! =null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
    Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
    binder.validate(validationHints);
    break; }}}}Copy the code

Thus, the logic of concrete validation exists in the abstract class of the parameter parser and applies to all parsers that inherit it. In addition, it can be seen from the code that the @validated and @valid annotations are identified. This is the general process of automatic validation by adding validation annotations to method parameters.