Back to Javatutorial

Spring У

docs/Spring全家桶/Spring/Spring中对于校验功能的支持.md

1.0.019.4 KB
Original Source

Spring У

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 һҪУģ󲿷£Ϊʽ

  • POSTPUT ʹ requestBody ݲ
  • GET ʹ requestParam/PathVariable ݲ

ʵϣ 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 {};

}

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 ӿԶУ顣

Ҫ

  • ʵ supports
  • ʵ validate
    • ͨ Errors ռ
      • ObjectErrorBean
      • FieldErrorBeanԣProperty
    • ͨ ObjectError 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;
    }

}

#ʧ(Fail Fast)

Spring Validation ĬϻУֶΣȻ׳쳣ͨһЩ򵥵ã Fali Fast ģʽһУʧܾء

@Bean
public Validator validator() {
    ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
            .configure()
            // ʧģʽ
            .failFast(true)
            .buildValidatorFactory();
    return validatorFactory.getValidator();
}

#Spring Уԭ

#Spring Уʹó

  • Spring У飨Validator
  • Spring ݰ󶨣DataBinder
  • Spring Web 󶨣WebDataBinder
  • Spring WebMVC/WebFlux У

#Validator ӿ

  • ӿְ
    • Spring ڲУӿڣ̵ͨķʽУĿ
  • ķ
    • supports(Class)УĿܷУ
    • validate(Object,Errors)УĿ󣬲Уʧܵ Errors
    • ռorg.springframework.validation.Errors
    • Validator ࣺorg.springframework.validation.ValidationUtils

#Errors ӿ

  • ӿְ
    • ݰ󶨺Уռӿڣ Java Bean ǿ
  • ķ
    • reject أռİ
    • rejectValue أռֶеĴİ
    • Java Bean org.springframework.validation.ObjectError
    • Java Bean Դorg.springframework.validation.FieldError

#Errors İԴ

Errors İɲ

  • ѡ Errors ʵ֣磺org.springframework.validation.BeanPropertyBindingResult
  • reject rejectValue
  • ȡ Errors ObjectError FieldError
  • ObjectError FieldError е code args MessageSource ʵ֣磺ResourceBundleMessageSource

#spring web Уԭ

#RequestBody Уʵԭ

spring-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 ķװ

#IJУʵԭ

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.Validator
  • ռorg.springframework.validation.Errors
  • Java Bean org.springframework.validation.ObjectError
  • Java Bean Դorg.springframework.validation.FieldError
  • Bean Validation 䣺org.springframework.validation.beanvalidation.LocalValidatorFactoryBean

#ο

ο

https://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