Spring Validation
1. Data verification based on method level
Check (Normal check + block check)
forservice
Layer 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@Override
Method 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@Lazy
Annotation 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.