Each one
To get rich, first build roads. To use, first ... is based on need and assembled, do not do technical castles in the air
Related Reading
[Java] small home-depth understanding of data validation: Java Bean Validation 2.0 (JSR303, JSR349, JSR380) Hibernate-Validation 6.x use cases
[small house] Java-depth understanding of data validation (Bean Validation): RBI Foundation Classes (ValidationProvider , ConstraintDescriptor, ConstraintValidator)
Foreword
The mighty general programmers are not very concerned about the Bean Validation
topic talked about for so long, the junior partner during wx I said he has not seen most want to see content, I asked what most want to see? He said that apparently the data verification in the Spring
use of ah. I think If the accident, this should be the common aspiration of many small partners of it, but the road is long Come, they have to search up and down, this will cut into the Spring greatest concern in the past -
To understand the depth Spring
of Bean Validation
support, org.springframework.validation.beanvalidation
this package inside these key API must thoroughly understand myself, since such re-use @Valid
combined with Spring
from time to time in order to more acting freely ~
Description: jar package is located
spring-context
, is the core function module Spring context
I class within this package screenshot below for reference:
Spring
Although not directly achieve this Bean check the JSR
specifications, but from the Spring3.0
beginning, Spring it provides Bean Validation
support.
- 3.0 provides a level of parity Bean
- 3.1 provides a more robust
方法级别
check
BeanValidationPostProcessor
It is ordinary BeanPostProcessor
. It can go check the Spring container Bean, you allow it to determine the initialization is complete.
For example, we do not allow some of Bean certain fields are empty, such as data link, user name and password, and so on, this time to spend it handles very elegant and advanced a ~
If the check is not passed, in violation of constraints will throw an exception that prevents normal start ~ container
public class BeanValidationPostProcessor implements BeanPostProcessor, InitializingBean {
// 这就是我们熟悉的校验器
// 请注意这里是javax.validation.Validator,而不是org.springframework.validation.Validator
@Nullable
private Validator validator;
// true:表示在Bean初始化之后完成校验
// false:表示在Bean初始化之前就校验
private boolean afterInitialization = false;
... // 省略get/set
// 由此可见使用的是默认的校验器(当然还是Hibernate的)
@Override
public void afterPropertiesSet() {
if (this.validator == null) {
this.validator = Validation.buildDefaultValidatorFactory().getValidator();
}
}
// 这个实现太简单了~~~
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (!this.afterInitialization) {
doValidate(bean);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (this.afterInitialization) {
doValidate(bean);
}
return bean;
}
protected void doValidate(Object bean) {
Assert.state(this.validator != null, "No Validator set");
Object objectToValidate = AopProxyUtils.getSingletonTarget(bean);
if (objectToValidate == null) {
objectToValidate = bean;
}
Set<ConstraintViolation<Object>> result = this.validator.validate(objectToValidate);
// 拼接错误消息最终抛出
if (!result.isEmpty()) {
StringBuilder sb = new StringBuilder("Bean state is invalid: ");
for (Iterator<ConstraintViolation<Object>> it = result.iterator(); it.hasNext();) {
ConstraintViolation<Object> violation = it.next();
sb.append(violation.getPropertyPath()).append(" - ").append(violation.getMessage());
if (it.hasNext()) {
sb.append("; ");
}
}
throw new BeanInitializationException(sb.toString());
}
}
}
The BeanValidationPostProcessor
realization of the function is indeed very simple, nothing more than for all of Bean initialized 前/后
for verification.
If we are to Spring Bean
want the constraints of words (such as for property, construction, etc.), it is very easy to use ~
Note:
BeanValidationPostProcessor
The default is fitted into the container but not the -
==org.springframework.validation.Validator==
Application-specific object validator, which is their Spring abstract, different from the attention javax.validation.Validator
. 这个接口完全脱离了任何基础设施或上下文
That is, it is not coupled to only validate the Web layer, the data access layer or any layer of the object. It supports any layer in the applied program
// 注意:它可不是Spring3后才推出的 最初就有
public interface Validator {
// 此clazz是否可以被validate
boolean supports(Class<?> clazz);
// 执行校验,错误消息放在Errors 装着
// 可以参考ValidationUtils这个工具类,它能帮助你很多
void validate(Object target, Errors errors);
}
It inheritance tree as follows:
SmartValidator
The sub-interface that extends the increased parity grouping : hints.
// @since 3.1 这个出现得比较晚
public interface SmartValidator extends Validator {
// 注意:这里的Hints最终都会被转化到JSR的分组里去~~
// 所以这个可变参数,传接口Class对象即可~
void validate(Object target, Errors errors, Object... validationHints);
// @since 5.1 简单的说,这个方法子类请复写 否则不能使用
default void validateValue(Class<?> targetType, String fieldName, @Nullable Object value, Errors errors, Object... validationHints) {
throw new IllegalArgumentException("Cannot validate individual value for " + targetType);
}
}
SpringValidatorAdapter
: Calibration adapter (important)
The implementation class Class is very important, it is javax.validation.Validator
the Spring of Validator
adaptation, it can be docked to the JSR checker to verify the completion of the work -
After Spring5.0, this class has the perfect support to achieve
Bean Validation 2.0
// @since 3.0
public class SpringValidatorAdapter implements SmartValidator, javax.validation.Validator {
// 通用的三个约束注解都需要有的属性
private static final Set<String> internalAnnotationAttributes = new HashSet<>(4);
static {
internalAnnotationAttributes.add("message");
internalAnnotationAttributes.add("groups");
internalAnnotationAttributes.add("payload");
}
// 最终都是委托给它来完成校验的~~~
@Nullable
private javax.validation.Validator targetValidator;
public SpringValidatorAdapter(javax.validation.Validator targetValidator) {
Assert.notNull(targetValidator, "Target Validator must not be null");
this.targetValidator = targetValidator;
}
// 简单的说:默认支持校验所有的Bean类型~~~
@Override
public boolean supports(Class<?> clazz) {
return (this.targetValidator != null);
}
// processConstraintViolations做的事一句话解释:
// 把ConstraintViolations错误消息,全都适配放在Errors(BindingResult)里面存储着
@Override
public void validate(Object target, Errors errors) {
if (this.targetValidator != null) {
processConstraintViolations(this.targetValidator.validate(target), errors);
}
}
@Override
public void validate(Object target, Errors errors, Object... validationHints) {
if (this.targetValidator != null) {
processConstraintViolations(this.targetValidator.validate(target, asValidationGroups(validationHints)), errors);
}
}
@SuppressWarnings("unchecked")
@Override
public void validateValue(Class<?> targetType, String fieldName, @Nullable Object value, Errors errors, Object... validationHints) {
if (this.targetValidator != null) {
processConstraintViolations(this.targetValidator.validateValue(
(Class) targetType, fieldName, value, asValidationGroups(validationHints)), errors);
}
}
// 把validationHints都转换为group (支识别Class类型哦)
private Class<?>[] asValidationGroups(Object... validationHints) {
Set<Class<?>> groups = new LinkedHashSet<>(4);
for (Object hint : validationHints) {
if (hint instanceof Class) {
groups.add((Class<?>) hint);
}
}
return ClassUtils.toClassArray(groups);
}
// 关于Implementation of JSR-303 Validator interface 省略...
}
This adapter it all the validation methods Spring interface, eventually entrusted to org.springframework.validation.Validator
, and so can be the perfect combination of JSR use, more powerful functions ~
Although this class it is a Class entity classes, but in general it is not recommended to be used directly
CustomValidatorBean
Configurable (the Custom) class of Bean, also achieved 双接口
. It can be configured ValidatorFactory
to verify factory, MessageInterpolator
interpolator etc ...
public class CustomValidatorBean extends SpringValidatorAdapter implements Validator, InitializingBean {
// javax.validation.ValidatorFactory
@Nullable
private ValidatorFactory validatorFactory;
@Nullable
private MessageInterpolator messageInterpolator;
@Nullable
private TraversableResolver traversableResolver;
... // 省略所有set方法(木有get方法)
// 默认设置~~~~初始化
@Override
public void afterPropertiesSet() {
if (this.validatorFactory == null) {
this.validatorFactory = Validation.buildDefaultValidatorFactory();
}
// 这一句就是new ValidatorContextImpl( this )
ValidatorContext validatorContext = this.validatorFactory.usingContext();
// 插值器
MessageInterpolator targetInterpolator = this.messageInterpolator;
if (targetInterpolator == null) {
targetInterpolator = this.validatorFactory.getMessageInterpolator();
}
validatorContext.messageInterpolator(new LocaleContextMessageInterpolator(targetInterpolator));
if (this.traversableResolver != null) {
validatorContext.traversableResolver(this.traversableResolver);
}
// 把已经配置好的这个Validator设置进去~
setTargetValidator(validatorContext.getValidator());
}
}
Nomenclature can be seen, it is a Bean, Spring container can be used together with. Spring
Although the interior is not used to it directly, but we have our own needs, then you can use it (in fact, more or use more subclasses) ~
== == LocalValidatorFactoryBean
It CustomValidatorBean
same level, are inherited from SpringValidatorAdapter
, but its ability to provide a more powerful, such as Spring
handling check this most important processor MethodValidationPostProcessor
is to rely on it to provide validator ~
It is the Spring
context javax.validation
of the central configuration classes.
// @since 3.0 这个类非常的丰富 实现了接口javax.validation.ValidatorFactory
// 实现了ApplicationContextAware拿到Spring上下文...
// 但其实,它的实际工作都是委托式,自己只提供了各式各样的配置~~~(主要是配置JSR)
public class LocalValidatorFactoryBean extends SpringValidatorAdapter implements ValidatorFactory, ApplicationContextAware, InitializingBean, DisposableBean {
... // 省略所有的配置属性
... // 省略所有的get/set
... // 省略afterPropertiesSet()进行的默认配置初始化 最终调用setTargetValidator(this.validatorFactory.getValidator());
// 备注:还记得上文吗?上文的validator校验器是从上下文拿的,这里是从工厂拿的
// 省略所有对ValidatorFactory接口的方法实现~
}
This class is very important, although it does not directly use Spring, but it is the cornerstone .
Note: Although the name suffix
FactoryBean
, but it is notorg.springframework.beans.factory.FactoryBean
this subclass interface.
In fact, this is a problem punctuation, punctuation correct way is: LocalValidatorFactory
Bean ~
OptionalValidatorFactoryBean
@since 4.0.1
We provided it does only one thing: let org.springframework.validation.Validator
become optional (even if not initialize successfully, it will not error, equivalent to abnormal eating Well ~)
// @since 4.0.1
public class OptionalValidatorFactoryBean extends LocalValidatorFactoryBean {
@Override
public void afterPropertiesSet() {
try {
super.afterPropertiesSet();
} catch (ValidationException ex) {
LogFactory.getLog(getClass()).debug("Failed to set up a Bean Validation provider", ex);
}
}
}
In summary, if you want to use org.springframework.validation.SmartValidator
to complete the verification of the Bean, it would be such a defined manually Bean, then call their own API verification completed check -
if you want it all for the notes can be programmed automatically check that heard below break it (is most concerned about, the most important element) ~
SpringConstraintValidatorFactory
ConstraintValidatorFactory
API has asked before the entire mentioned, this is based Spring
on its expansion, so that the container and integrated Spring ~
public class SpringConstraintValidatorFactory implements ConstraintValidatorFactory {
private final AutowireCapableBeanFactory beanFactory;
public SpringConstraintValidatorFactory(AutowireCapableBeanFactory beanFactory) {
Assert.notNull(beanFactory, "BeanFactory must not be null");
this.beanFactory = beanFactory;
}
// 注意:此处是直接调用了create方法,放进容器
@Override
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
return this.beanFactory.createBean(key);
}
// Bean Validation 1.1 releaseInstance method
public void releaseInstance(ConstraintValidator<?, ?> instance) {
this.beanFactory.destroyBean(instance);
}
}
MessageSourceResourceBundleLocator
This class is also very interesting, it extends the Hibernate
package of ResourceBundleLocator
international, and use
Spring's own international resources:org.springframework.context.MessageSource
Description:
ResourceBundleLocator
itHibernate
's a SPI,Hibernate
inside himself, but it also has to realize oh ~ (Bean Validation
a lot of uses internal SPI technology, are interested can understand)
public class MessageSourceResourceBundleLocator implements ResourceBundleLocator {
private final MessageSource messageSource;
public MessageSourceResourceBundleLocator(MessageSource messageSource) {
Assert.notNull(messageSource, "MessageSource must not be null");
this.messageSource = messageSource;
}
@Override
public ResourceBundle getResourceBundle(Locale locale) {
return new MessageSourceResourceBundle(this.messageSource, locale);
}
}
About MessageSourceResourceBundle
it, relatively familiar point, it is not a dedicated check, is used to handle international resources on Spring as a whole: MessageSource
, java.util.ResourceBundl
help class ~
//@since 27.02.2003 java.util.ResourceBundle 它是JDK提供来读取国际化的属性配置文件的 是个抽象类
public class MessageSourceResourceBundle extends ResourceBundle {
private final MessageSource messageSource;
private final Locale locale;
public MessageSourceResourceBundle(MessageSource source, Locale locale) {
Assert.notNull(source, "MessageSource must not be null");
this.messageSource = source;
this.locale = locale;
}
public MessageSourceResourceBundle(MessageSource source, Locale locale, ResourceBundle parent) {
this(source, locale);
setParent(parent);
}
@Override
@Nullable
protected Object handleGetObject(String key) {
try {
return this.messageSource.getMessage(key, null, this.locale);
} catch (NoSuchMessageException ex) {
return null;
}
}
// @since 1.6
@Override
public boolean containsKey(String key) {
try {
this.messageSource.getMessage(key, null, this.locale);
return true;
}
catch (NoSuchMessageException ex) {
return false;
}
}
@Override
public Enumeration<String> getKeys() {
throw new UnsupportedOperationException("MessageSourceResourceBundle does not support enumerating its keys");
}
@Override
public Locale getLocale() {
return this.locale;
}
}
Spring
Environment can be used not only Hibernate
international document, it can also help MessageSourceResourceBundleLocator
out their own.
LocaleContextMessageInterpolator
It is a javax.validation.MessageInterpolator
interpolator, Spring and own it LocaleContext
combines a ~
// @since 3.0
// org.springframework.context.i18n.LocaleContextHolder#getLocale()
public class LocaleContextMessageInterpolator implements MessageInterpolator {
private final MessageInterpolator targetInterpolator;
public LocaleContextMessageInterpolator(MessageInterpolator targetInterpolator) {
Assert.notNull(targetInterpolator, "Target MessageInterpolator must not be null");
this.targetInterpolator = targetInterpolator;
}
@Override
public String interpolate(String message, Context context) {
return this.targetInterpolator.interpolate(message, context, LocaleContextHolder.getLocale());
}
@Override
public String interpolate(String message, Context context, Locale locale) {
return this.targetInterpolator.interpolate(message, context, locale);
}
}
Demo Show
After much deliberation, Demo or give it a very simple operation, I'm here to CustomValidatorBean
an example of the Bean verify:
@Getter
@Setter
@ToString
public class Person {
// 错误消息message是可以自定义的
@NotNull(message = "{message} -> 名字不能为null", groups = Simple.class)
public String name;
@Max(value = 10, groups = Simple.class)
@Positive(groups = Default.class) // 内置的分组:default
public Integer age;
@NotNull(groups = Complex.class)
@NotEmpty(groups = Complex.class)
private List<@Email String> emails;
@Future(groups = Complex.class)
private Date start;
// 定义两个组 Simple组和Complex组
public interface Simple {
}
public interface Complex {
}
}
Like container into a validator:
@Configuration
public class RootConfig {
@Bean
public CustomValidatorBean customValidatorBean() {
return new CustomValidatorBean();
}
}
Using this calibration verifier Person object (for simplicity this article will direct new Kazakhstan, of course, you can also be a Bean object within the container)
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class})
public class TestSpringBean {
@Autowired
private SmartValidator smartValidator;
@Test
public void test1() {
Person person = new Person();
person.setAge(-1);
person.setStart(new Date());
Errors errors = new DirectFieldBindingResult(person, "person");
ValidationUtils.invokeValidator(smartValidator, person, errors, Person.Complex.class);
System.out.println(errors);
}
}
Printout:
org.springframework.validation.DirectFieldBindingResult: 3 errors
Field error in object 'person' on field 'emails': rejected value [null]; codes [NotEmpty.person.emails,NotEmpty.emails,NotEmpty.java.util.List,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [person.emails,emails]; arguments []; default message [emails]]; default message [不能为空]
Field error in object 'person' on field 'start': rejected value [Fri Jul 26 11:12:21 CST 2019]; codes [Future.person.start,Future.start,Future.java.util.Date,Future]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [person.start,start]; arguments []; default message [start]]; default message [需要是一个将来的时间]
Field error in object 'person' on field 'emails': rejected value [null]; codes [NotNull.person.emails,NotNull.emails,NotNull.java.util.List,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [person.emails,emails]; arguments []; default message [emails]]; default message [不能为null]
In line with expectations.
Note: Because the front said
Bean Validation
checksum large classes are thread safe inside, including the checkerjavax.validation.Validator
is thread-safe -
to sum up
From the beginning of this article, with respect to Bean Validation
this application will cut into Spring Lane. This paper describes some of the support classes, we understand that it can be done manually check for Spring Bean, but obviously not to do so in practical applications, after all, everything needs to advocate automation Well ~
Under == a, which is the entire Bean Validation
main course, which is the way to really check used in the enterprise · Spring · application analysis, which is familiar @Valid,@Validated
calibration problem and cascade property, thumbs welcome attention ~ ==
Knowledge Exchange
若文章格式混乱,可点击
: Description link - text link - text link - text link - text link
== The last: If you think this article helpful to you, may wish to point a praise chant. Of course, your circle of friends to share so that more small partners also are seeing 作者本人许可的~
==
If interested in technology content can join the group wx exchange: Java高工、架构师3群
.
If the group fails two-dimensional code, please add wx number: fsx641385712
(or two-dimensional code is scanned beneath wx). And Note: "java入群"
the word, will be invited into the group manually