Spring detail the core API for data validation support: SmartValidator

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)


Spring may be interested in adding wx scan code group: `Java senior engineers, architects' group 3 (the end the two-dimensional code)

Foreword

The mighty general programmers are not very concerned about the Bean Validationtopic 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 Springuse 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 Springof Bean Validationsupport, org.springframework.validation.beanvalidationthis package inside these key API must thoroughly understand myself, since such re-use @Validcombined with Springfrom 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:
Here Insert Picture Description

SpringAlthough not directly achieve this Bean check the JSRspecifications, but from the Spring3.0beginning, Spring it provides Bean Validationsupport.

  1. 3.0 provides a level of parity Bean
  2. 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 BeanValidationPostProcessorrealization of the function is indeed very simple, nothing more than for all of Bean initialized 前/后for verification.
If we are to Spring Beanwant the constraints of words (such as for property, construction, etc.), it is very easy to use ~

Note: BeanValidationPostProcessorThe 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:
Here Insert Picture Description

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.Validatorthe Spring of Validatoradaptation, 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 achieveBean 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 ValidatorFactoryto verify factory, MessageInterpolatorinterpolator 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. SpringAlthough 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 CustomValidatorBeansame level, are inherited from SpringValidatorAdapter, but its ability to provide a more powerful, such as Springhandling check this most important processor MethodValidationPostProcessoris to rely on it to provide validator ~

It is the Springcontext javax.validationof 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 not org.springframework.beans.factory.FactoryBeanthis subclass interface.
In fact, this is a problem punctuation, punctuation correct way is: Local ValidatorFactoryBean ~

OptionalValidatorFactoryBean

@since 4.0.1We provided it does only one thing: let org.springframework.validation.Validatorbecome 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.SmartValidatorto 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

ConstraintValidatorFactoryAPI has asked before the entire mentioned, this is based Springon 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 Hibernatepackage of ResourceBundleLocatorinternational, and use
Spring's own international resources:org.springframework.context.MessageSource

Description: ResourceBundleLocatorit Hibernate's a SPI, Hibernateinside himself, but it also has to realize oh ~ ( Bean Validationa 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 MessageSourceResourceBundleit, relatively familiar point, it is not a dedicated check, is used to handle international resources on Spring as a whole: MessageSource, java.util.ResourceBundlhelp 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;
    }
}

SpringEnvironment can be used not only Hibernateinternational document, it can also help MessageSourceResourceBundleLocatorout their own.

LocaleContextMessageInterpolator

It is a javax.validation.MessageInterpolatorinterpolator, Spring and own it LocaleContextcombines 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 CustomValidatorBeanan 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 Validationchecksum large classes are thread safe inside, including the checker javax.validation.Validatoris thread-safe -

to sum up

From the beginning of this article, with respect to Bean Validationthis 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 Validationmain course, which is the way to really check used in the enterprise · Spring · application analysis, which is familiar @Valid,@Validatedcalibration 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

Guess you like

Origin www.cnblogs.com/fangshixiang/p/11249498.html