Bean Validation完结篇:你必须关注的边边角角(约束级联、自定义约束、自定义校验器、国际化失败消息…)

  • 2019 年 10 月 3 日
  • 筆記

????

?????????????????????????

????

???Java??????????Java Bean Validation 2.0?JSR303?JSR349?JSR380?Hibernate-Validation 6.x????
???Spring?Spring?????????@Validated + MethodValidationPostProcessor???????????
???Java??????????Bean Validation????????@Valid??????????????????????


?Spring????????wx??`Java??????3?`????????


??

???????web????????????????????????JavaScript?????????????????????????????????web???????????????????????JS????(???????jQuery Validation Plugin)??????????

?????????????Bean Validation????????????????????????????????????????????????????????Bean Validation???????????????????????????~

?????????????????????~

BV(Bean Validation)?????

??????????(???????????????)??????
Bean Validation?????????????????????, ??????????, ??web????????Swing???????????GUI????

???????????????
?????????
Bean Validation??????Bean??????????????????????????API???

??????API??????????????????????????????????????????????????????????????????

????

?????????????????????????????????????????????

// child?person???????????  public class Child extends Person {      ...  }

????????????????????????????????

??????????

???????????????????????@Valid???????????????????????@Valid?????????????????????????Map?????????????????

Demo?

@Getter  @Setter  @ToString  public class Person {        @NotNull      private String name;      @NotNull      @Positive      private Integer age;        @Valid      @NotNull      private InnerChild child;      @Valid // ????List???????      private List<InnerChild> childList;        @Getter      @Setter      @ToString      public static class InnerChild {          @NotNull          private String name;          @NotNull          @Positive          private Integer age;      }    }

?????

    public static void main(String[] args) {          Person person = new Person();          person.setName("fsx");          Person.InnerChild child = new Person.InnerChild();          child.setName("fsx-age");          child.setAge(-1);          person.setChild(child);            // ??childList          person.setChildList(new ArrayList<Person.InnerChild>(){{              Person.InnerChild innerChild = new Person.InnerChild();              innerChild.setName("innerChild1");              innerChild.setAge(-11);              add(innerChild);                innerChild = new Person.InnerChild();              innerChild.setName("innerChild2");              innerChild.setAge(-12);              add(innerChild);          }});            Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(false)                  .buildValidatorFactory().getValidator();          Set<ConstraintViolation<Person>> result = validator.validate(person);            // ??????          result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())                  .forEach(System.out::println);      }

??????????

age ???null: null  childList[0].age ?????: -11  child.age ?????: -1  childList[1].age ?????: -12

??????message???

??????????????????????????message???????????????,?????????????message??????????????????message??????????

????????,??????????,??????MessageInterpolator???????????????????????????, ????????????????
?????????org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator????org.hibernate.validator.spi.resourceloading.ResourceBundleLocator?????????????????????~

?????????????PlatformResourceBundleLocator????Configuration????????????

    private ConfigurationImpl() {          this.validationBootstrapParameters = new ValidationBootstrapParameters();            // ?????????????USER_VALIDATION_MESSAGES???ValidationMessages          // ?????????????~~~~          this.defaultResourceBundleLocator = new PlatformResourceBundleLocator(                  ResourceBundleMessageInterpolator.USER_VALIDATION_MESSAGES          );          this.defaultTraversableResolver = TraversableResolvers.getDefault();          this.defaultConstraintValidatorFactory = new ConstraintValidatorFactoryImpl();          this.defaultParameterNameProvider = new DefaultParameterNameProvider();          this.defaultClockProvider = DefaultClockProvider.INSTANCE;      }

?????????????????( ???????????????{xxx})?
???message????????????message???{javax.validation.constraints.NotNull.message}????

public abstract class AbstractMessageInterpolator implements MessageInterpolator {      ...      private String interpolateMessage(String message, Context context, Locale locale) throws MessageDescriptorFormatException {          // ??message??????????????  ?????~          // ?????????????~~~          if ( message.indexOf( '{' ) < 0 ) {              return replaceEscapedLiterals( message );          }            // ??resolveMessage????message??????el???          if ( cachingEnabled ) {              resolvedMessage = resolvedMessages.computeIfAbsent( new LocalizedMessage( message, locale ), lm -> resolveMessage( message, locale ) );          } else {              resolvedMessage = resolveMessage( message, locale );          }          ...      }        private String resolveMessage(String message, Locale locale) {          String resolvedMessage = message;            // ????ResourceBundle???          ResourceBundle userResourceBundle = userResourceBundleLocator.getResourceBundle( locale );          ResourceBundle constraintContributorResourceBundle = contributorResourceBundleLocator.getResourceBundle( locale );          ResourceBundle defaultResourceBundle = defaultResourceBundleLocator.getResourceBundle( locale );          ...      }  }

???message????????????

  1. ???????{?????????????????message???????????????~
  2. ??????EL???resolveMessage()??????????????~
  3. ??????????????????
    1. userResourceBundleLocator???????classpath??????????????ValidationMessages.properties??????????????
    2. contributorResourceBundleLocator?????????
    3. defaultResourceBundle???????????/org/hibernate/validator??ValidationMessages.properties
  4. ????????????????????????????????????????????????????????????????????
    1. ?????????classpath???????????????????????????~~~
    2. ?????????????????????????????????????????null?~?

?????????{???????????????{?????{

????????????????message???????????????????

    @Min(value = 10, message = "{com.fsx.my.min.message}")      private Integer age;

?????????????ValidationMessages.properties??????????????

// ?????????{value}???????????  com.fsx.my.min.message=[?????]??????{value}

??????????????????

age [?????]??????10: -1

???????????

?????????????????????ValidationMessages_zh_CN.properties???????????Hibernate Validation???Locale??????


Spring???????????

??????Hibernate Validation????????????????????????Spring?????????????????Spring????????????????Spring MVC??????????????????????????????Spring?????????????Demo??

??????Spring?????????Hibernate Validation???????????????~

1?????????????????????????

@Configuration  public class RootConfig {        @Bean      public LocalValidatorFactoryBean localValidatorFactoryBean() {          LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();            // ??Spring?????????          //ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();          //messageSource.setBasename("MyValidationMsg");            ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();          messageSource.setBasename("MyValidationMsg"); // ???????????????spring?`.properties`??????          messageSource.setCacheSeconds(120); // ????          // messageSource.setFileEncodings(); // ???? UTF-8            localValidatorFactoryBean.setValidationMessageSource(messageSource);          return localValidatorFactoryBean;      }  }

?????

@Slf4j  @RunWith(SpringJUnit4ClassRunner.class)  @ContextConfiguration(classes = {RootConfig.class})  public class TestSpringBean {        @Autowired      private LocalValidatorFactoryBean localValidatorFactoryBean;        @Test      public void test1() {          Person person = new Person();          person.setAge(-5);            Validator validator = localValidatorFactoryBean.getValidator();          Set<ConstraintViolation<Person>> result = validator.validate(person);            // ??????          result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())                  .forEach(System.out::println);      }    }

?????????????????

age [?????]??????10: -5

?????Spring????????????????????????Spring??????????Hibernate~???Spring???????????????????


Spring MVC???????????Validator

Spring MVC?????????????????????

public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {      ...      @Bean      public Validator mvcValidator() {          Validator validator = getValidator();          if (validator == null) {              if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) {                  Class<?> clazz;                  try {                      String className = "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean";                      clazz = ClassUtils.forName(className, WebMvcConfigurationSupport.class.getClassLoader());                  } catch (ClassNotFoundException | LinkageError ex) {                      throw new BeanInitializationException("Failed to resolve default validator class", ex);                  }                  validator = (Validator) BeanUtils.instantiateClass(clazz);              } else {                  validator = new NoOpValidator();              }          }          return validator;      }      ...  }

????????????????????

  1. Spring MVC???????????????javax.validation.Validator??????new NoOpValidator()???????
  2. Spring MVC???????????OptionalValidatorFactoryBean?LocalValidatorFactoryBean????~
  3. ?????????@EnableWebMvc??????SpringBoot?????

???????????????????????

@Configuration  @EnableWebMvc  public class WebMvcConfig extends WebMvcConfigurerAdapter {      ...      @Override      public Validator getValidator() {          // return "global" validator          return new LocalValidatorFactoryBean();      }      ...  }

?????????@InitBinder??????????????????Controller??????????????????????????????????????????

==?????==

JSR?Hibernate???????????????????????????????????????????????????????????????????

JSR?Hibernate???????????????Java??????????Bean Validation????????@Valid??????????????????????

???????????????2?????

  1. ?????????
  2. ???????(?????ConstraintValidator)
  3. ???????????

??Demo????????????????????????@CollectionRange

1?????????????????

@Documented  @Constraint(validatedBy = {})  @SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)  @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})  @Retention(RUNTIME)  @Repeatable(value = CollectionRange.List.class)  @Size // ???????Size???  ????????????~~~  @ReportAsSingleViolation // ???????????  public @interface CollectionRange {        // ?????????      String message() default "{com.fsx.my.collection.message}";      Class<?>[] groups() default {};      Class<? extends Payload>[] payload() default {};        // ?????  @OverridesAttribute???????????~~~~~~ ?????????????      @OverridesAttribute(constraint = Size.class, name = "min")      int min() default 0;      @OverridesAttribute(constraint = Size.class, name = "max")      int max() default Integer.MAX_VALUE;        // ????      @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})      @Retention(RUNTIME)      @Documented      public @interface List {          CollectionRange[] value();      }  }

2????????
???????????
3????????
?????????message????????????????~

com.fsx.my.collection.message=[?????]???????????{min}?{max}?????????

?????

@Getter  @Setter  @ToString  public class Person {      @CollectionRange(min = 5, max = 10)      private List<Integer> numbers;  }        // ????      public static void main(String[] args) {          Person person = new Person();          person.setNumbers(Arrays.asList(1, 2, 3));            Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(false)                  .buildValidatorFactory().getValidator();          Set<ConstraintViolation<Person>> result = validator.validate(person);            // ??????          result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())                  .forEach(System.out::println);      }

???????????????

numbers [?????]???????????5?10?????????: [1, 2, 3]

????

???????????????????????????????0???????????????

  1. ??????????????????
  2. ??????????????????????????????????????

???message??==??????==

???????????message??????{}?????????????????????????????????????????????

but???????????????????message???????????????????????????????????????????????????

????????????

@Documented  @Retention(RUNTIME)  @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})  @Constraint(validatedBy = {GenderConstraintValidator.class})  public @interface Gender {        // ?????????      String message() default "{com.fsx.my.gender.message}";      Class<?>[] groups() default {};      Class<? extends Payload>[] payload() default {};        int gender() default 0; //0???  1???  }

?????????

com.fsx.my.gender.message=[?????]?????????[{zhGenderValue}]?

????????????zhGenderValue????????????????????????????????????

public class GenderConstraintValidator implements ConstraintValidator<Gender, Integer> {        int genderValue;        @Override      public void initialize(Gender constraintAnnotation) {          genderValue = constraintAnnotation.gender();      }        @Override      public boolean isValid(Integer value, ConstraintValidatorContext context) {          //????  ?????????          HibernateConstraintValidatorContext hibernateContext = context.unwrap(HibernateConstraintValidatorContext.class);          hibernateContext.addMessageParameter("zhGenderValue", genderValue == 0 ? "?" : "?"); // ????          //hibernateContext.buildConstraintViolationWithTemplate("{zhGenderValue}").addConstraintViolation();            if (value == null) {              return false; // null is not valid          }          return value == genderValue;      }  }

?????

@Getter  @Setter  @ToString  public class Person {      @Gender(gender = 0)      private Integer personGender;  }        public static void main(String[] args) {          Person person = new Person();          person.setPersonGender(1);            Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(false)                  .buildValidatorFactory().getValidator();          Set<ConstraintViolation<Person>> result = validator.validate(person);            // ??????          result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())                  .forEach(System.out::println);      }

?????

personGender [?????]?????????[?]?: 1

????????

??

???????????????????????Bean Validation?java?????bean?????????????2.x?hibernate validator????????????????????????????????????????????????
????Bean Validation????????????????????????????????????????~

????

????????????????-????-????-????-????

==The last????????????????????????????????????????????????~==

?????????????wx????Java??????3??
??????????wx??fsx641385712???????wx??????????"java??" ??????????