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??"
??????????