docs/Spring全家桶/Spring/Spring中对于校验功能的支持.md
Java API 淶(JSR303)BeanУıvalidation-apiûṩʵ֡hibernate validationǶ淶ʵ֣Уע@Email``@LengthȡSpring ValidationǶhibernate validationĶηװ֧spring mvcԶУ顣
spring-boot 汾С 2.3.xspring-boot-starter-web Զ hibernate-validator spring-boot 汾 2.3.xҪֶ
<dependency>
<groupId>org.hibernate.validator</groupId>
hibernate-validator-parent
<version>6.2.5.Final</version>
</dependency>
web ˵ΪֹǷҵӰ죬 Controller һҪУģ£Ϊʽ
ʵϣ requestBody У黹ǷУ飬նǵ Hibernate Validator ִУ飬Spring Validation ֻһװ
1ʵϱУע
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
@NotNull
private Long id;
@NotBlank
@Size(min = 2, max = 10)
private String name;
@Min(value = 1)
@Max(value = 100)
private Integer age;
}
2ڷУע
@Slf4j
@Validated
@RestController
@RequestMapping("validate1")
public class ValidatorController {
/**
* {@link RequestBody} У
*/
@PostMapping(value = "save")
public DataResult<Boolean> save(@Valid @RequestBody User entity) {
log.info("һ¼{}", JSONUtil.toJsonStr(entity));
return DataResult.ok(true);
}
/**
* {@link RequestParam} У
*/
@GetMapping(value = "queryByName")
public DataResult<User> queryByName(
@RequestParam("username")
@NotBlank
@Size(min = 2, max = 10)
String name
) {
User user = new User(1L, name, 18);
return DataResult.ok(user);
}
/**
* {@link PathVariable} У
*/
@GetMapping(value = "detail/{id}")
public DataResult<User> detail(@PathVariable("id") @Min(1L) Long id) {
User user = new User(id, "", 18);
return DataResult.ok(user);
}
}
3У׳ ConstraintViolationException MethodArgumentNotValidException 쳣
ʵĿУͨͳһ쳣һѺõʾ
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* в֪쳣
*/
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(Throwable.class)
public Result handleException(Throwable e) {
log.error("δ֪쳣", e);
return new Result(ResultStatus.HTTP_SERVER_ERROR.getCode(), e.getMessage());
}
/**
* ͳһУ쳣(ͨ)
*
* @param e ConstraintViolationException
* @return {@link DataResult}
*/
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({ ConstraintViolationException.class })
public Result handleConstraintViolationException(final ConstraintViolationException e) {
log.error("ConstraintViolationException", e);
List<String> errors = new ArrayList<>();
for (ConstraintViolation<?> violation : e.getConstraintViolations()) {
Path path = violation.getPropertyPath();
List<String> pathArr = StrUtil.split(path.toString(), ',');
errors.add(pathArr.get(0) + " " + violation.getMessage());
}
return new Result(ResultStatus.REQUEST_ERROR.getCode(), CollectionUtil.join(errors, ","));
}
/**
* У쳣
*
* @param e MethodArgumentNotValidException
* @return {@link DataResult}
*/
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({ MethodArgumentNotValidException.class })
private Result handleMethodArgumentNotValidException(final MethodArgumentNotValidException e) {
log.error("MethodArgumentNotValidException", e);
List<String> errors = new ArrayList<>();
for (ObjectError error : e.getBindingResult().getAllErrors()) {
errors.add(((FieldError) error).getField() + " " + error.getDefaultMessage());
}
return new Result(ResultStatus.REQUEST_ERROR.getCode(), CollectionUtil.join(errors, ","));
}
}
ʵĿУܶҪʹͬһ DTO ղͬУܿDzһġʱ DTO ֶϼԼע⡣ˣspring-validation ֧˷УĹܣר⡣
1
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface AddCheck { }
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface EditCheck { }
2ʵϱУע
@Data
public class User2 {
@NotNull(groups = EditCheck.class)
private Long id;
@NotNull(groups = { AddCheck.class, EditCheck.class })
@Size(min = 2, max = 10, groups = { AddCheck.class, EditCheck.class })
private String name;
@IsMobile(message = "Чֻ", groups = { AddCheck.class, EditCheck.class })
private String mobile;
}
3ڷϸݲͬУ
@Slf4j
@Validated
@RestController
@RequestMapping("validate2")
public class ValidatorController2 {
/**
* {@link RequestBody} У
*/
@PostMapping(value = "add")
public DataResult<Boolean> add(@Validated(AddCheck.class) @RequestBody User2 entity) {
log.info("һ¼{}", JSONUtil.toJsonStr(entity));
return DataResult.ok(true);
}
/**
* {@link RequestBody} У
*/
@PostMapping(value = "edit")
public DataResult<Boolean> edit(@Validated(EditCheck.class) @RequestBody User2 entity) {
log.info("༭һ¼{}", JSONUtil.toJsonStr(entity));
return DataResult.ok(true);
}
}
ǰʾУDTO ֶζǻͺ String ͡ʵʳУпijֶҲһȣʹǶУ顣 post 磬汣 User Ϣʱͬʱ Job ϢҪעǣʱ DTO ĶӦֶα@Valid ע⡣
@Data
public class UserDTO {
@Min(value = 10000000000000000L, groups = Update.class)
private Long userId;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String userName;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String account;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String password;
@NotNull(groups = {Save.class, Update.class})
@Valid
private Job job;
@Data
public static class Job {
@Min(value = 1, groups = Update.class)
private Long jobId;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String jobName;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String position;
}
/**
* ʱУ
*/
public interface Save {
}
/**
* µʱУ
*/
public interface Update {
}
}
ƴ
ǶУԽϷУһʹáоǶУԼÿһУ飬List<Job>ֶλ list ÿһ Job У
1ԶУע @IsMobile
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Constraint(validatedBy = MobileValidator.class)
public @interface IsMobile {
String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
2ʵ ConstraintValidator ӿڣд @IsMobile УעĽ
import cn.hutool.core.util.StrUtil;
import io.github.dunwu.spring.core.validation.annotation.IsMobile;
import io.github.dunwu.tool.util.ValidatorUtil;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class MobileValidator implements ConstraintValidator<IsMobile, String> {
@Override
public void initialize(IsMobile isMobile) { }
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if (StrUtil.isBlank(s)) {
return false;
} else {
return ValidatorUtil.isMobile(s);
}
}
}
ͨʵ org.springframework.validation.Validator ӿԶУ顣
Ҫ
supportsvalidate
Errors ռ
ObjectErrorBeanFieldErrorBeanԣPropertyObjectError FieldError MessageSource ʵֻȡյĴİpackage io.github.dunwu.spring.core.validation;
import io.github.dunwu.spring.core.validation.annotation.Valid;
import io.github.dunwu.spring.core.validation.config.CustomValidatorConfig;
import io.github.dunwu.spring.core.validation.entity.Person;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Component
public class CustomValidator implements Validator {
private final CustomValidatorConfig validatorConfig;
public CustomValidator(CustomValidatorConfig validatorConfig) {
this.validatorConfig = validatorConfig;
}
/**
* Уֻ Person У
*/
@Override
public boolean supports(Class<?> clazz) {
return Person.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "name", "name.empty");
List<Field> fields = getFields(target.getClass());
for (Field field : fields) {
Annotation[] annotations = field.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType().getAnnotation(Valid.class) != null) {
try {
ValidatorRule validatorRule = validatorConfig.findRule(annotation);
if (validatorRule != null) {
validatorRule.valid(annotation, target, field, errors);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
private List<Field> getFields(Class<?> clazz) {
// Field
List<Field> fields = new ArrayList<>();
// classͲΪ
while (clazz != null) {
// Ե
Collections.addAll(fields, clazz.getDeclaredFields());
clazz = clazz.getSuperclass();
}
return fields;
}
}
Spring Validation ĬϻУֶΣȻ׳쳣ͨһЩã Fali Fast ģʽһУʧܾء
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// ʧģʽ
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
supports(Class)УĿܷУvalidate(Object,Errors)УĿУʧܵ Errorsorg.springframework.validation.Errorsorg.springframework.validation.ValidationUtilsreject أռİrejectValue أռֶеĴİorg.springframework.validation.ObjectErrororg.springframework.validation.FieldErrorErrors İɲ
org.springframework.validation.BeanPropertyBindingResultResourceBundleMessageSourcespring-mvc УRequestResponseBodyMethodProcessor ڽ @RequestBody עIJԼ@ResponseBody עķֵġУִвУ϶ڽķ resolveArgument() У
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
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) {
// ԽвУ
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
// У׳ MethodArgumentNotValidException
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
ԿresolveArgument() validateIfApplicable()вУ顣
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
// ȡע⣬ @RequestBody@Valid@Validated
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation ann : annotations) {
// ȳԻȡ @Validated ע
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
// ע @ValidatedֱӿʼУ顣
// ûУôжϲǰǷ Valid ͷע⡣
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;
}
}
}
ϴ룬ͽ Spring Ϊʲôͬʱ֧ @Validated``@Valid ע⡣
һ WebDataBinder.validate() ʵ֣
@Override
public void validate(Object target, Errors errors, Object... validationHints) {
if (this.targetValidator != null) {
processConstraintViolations(
// ˴ Hibernate Validator ִУ
this.targetValidator.validate(target, asValidationGroups(validationHints)), errors);
}
}
ͨ룬Կ Spring Уʵǻ Hibernate Validator ķװ
Spring ָ֧ݷȥءУ飬ԭӦ AOP ˵ͨ MethodValidationPostProcessor ̬ע AOP 棬Ȼʹ MethodValidationInterceptor е㷽֯ǿ
public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessorimplements InitializingBean {
@Override
public void afterPropertiesSet() {
// Ϊ @Validated ע Bean
Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
// Advisor ǿ
this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
}
// Adviceʾһ
protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
}
}
ſһ MethodValidationInterceptor
public class MethodValidationInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// ǿķֱ
if (isFactoryBeanMetadataMethod(invocation.getMethod())) {
return invocation.proceed();
}
// ȡϢ
Class<?>[] groups = determineValidationGroups(invocation);
ExecutableValidator execVal = this.validator.forExecutables();
Method methodToValidate = invocation.getMethod();
Set<ConstraintViolation<Object>> result;
try {
// У飬ջίи Hibernate Validator У
result = execVal.validateParameters(
invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
}
catch (IllegalArgumentException ex) {
...
}
// 쳣ֱ׳
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
// ķ
Object returnValue = invocation.proceed();
// ԷֵУ飬ջίиHibernate ValidatorУ
result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups);
// 쳣ֱ׳
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
return returnValue;
}
}
ʵϣ requestBody У黹ǷУ飬նǵ Hibernate Validator ִУ飬Spring Validation ֻһװ
Spring ЩУ
org.springframework.validation.Validatororg.springframework.validation.Errorsorg.springframework.validation.ObjectErrororg.springframework.validation.FieldErrororg.springframework.validation.beanvalidation.LocalValidatorFactoryBeanhttps://www.w3cschool.cn/wkspring https://www.runoob.com/w3cnote/basic-knowledge-summary-of-spring.html http://codepub.cn/2015/06/21/Basic-knowledge-summary-of-Spring https://dunwu.github.io/spring-tutorial https://mszlu.com/java/spring http://c.biancheng.net/spring/aop-module.html