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??????????????????????
??
???????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????????????
- ???????
{?????????????????message???????????????~ - ??????EL???
resolveMessage()??????????????~ - ??????????????????
1.userResourceBundleLocator???????classpath??????????????ValidationMessages.properties??????????????
2.contributorResourceBundleLocator?????????
3.defaultResourceBundle???????????/org/hibernate/validator??ValidationMessages.properties - ????????????????????????????????????????????????????????????????????
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; } ... }
????????????????????
Spring MVC???????????????javax.validation.Validator??????new NoOpValidator()???????Spring MVC???????????OptionalValidatorFactoryBean?LocalValidatorFactoryBean????~- ?????????
@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?????
- ?????????
- ???????(?????
ConstraintValidator) - ???????????
??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???????????????
- ??????????????????
- ??????????????????????????????????????
???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??" ??????????
