preface
Generally, the Spring Boot project introduces the specific implementation dependency of the validation API under the Java EE specification, such as hibernate validator, to implement annotation based parameter and result verification at the method level, such as:
// The implementation class should be annotated with @ Validated public interface PersonService { @NotNull Person getOne(@NotBlank String name); List<@Valid @NotNull Person> getList(); Result<@Valid @NotNull Person> getResult(); } public class Person { @NotNull private String name; }
- For the method getOne, if the passed parameter name is empty or the returned result is null, a verification exception will be thrown
- For the method getList, if there is NULL in the returned result set or the result set does not comply with the verification rules (for example, Person.name is empty), a verification exception will be thrown
- For the getResult method, we want it to have the same verification rules as getList, which is not feasible by default (because the Validator does not know how to get the results from the custom result set)
- If we want the getResult method to achieve our expected verification effect, we need to implement a custom ValueExtractor and obtain the corresponding Validator based on it. This article also intends to expand based on the LocalValidatorFactoryBean provided by Spring to achieve this purpose
ValidationAutoConfiguration
@Configuration(proxyBeanMethods = false) // ExecutableValidator dependent on classpath @ConditionalOnClass(ExecutableValidator.class) // The corresponding file that depends on the implementation class (such as hibernate validator) @ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider") @Import(PrimaryDefaultValidatorPostProcessor.class) public class ValidationAutoConfiguration { // Spring's registration of ValidatorFactory Validator and other components is encapsulated in LocalValidatorFactoryBean @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) @ConditionalOnMissingBean(Validator.class) public static LocalValidatorFactoryBean defaultValidator(ApplicationContext applicationContext) { LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean(); MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory(applicationContext); factoryBean.setMessageInterpolator(interpolatorFactory.getObject()); return factoryBean; } // Here is the key to enable method parameter verification based on annotations in Spring @Bean @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment, @Lazy Validator validator, ObjectProvider<MethodValidationExcludeFilter> excludeFilters) { FilteredMethodValidationPostProcessor processor = new FilteredMethodValidationPostProcessor( excludeFilters.orderedStream()); boolean proxyTargetClass = environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true); processor.setProxyTargetClass(proxyTargetClass); processor.setValidator(validator); return processor; } }
This is the validation auto assembly class provided by SpringBoot
- Generally, after we introduce the specific implementation dependencies of the validation API under the Java EE specification, such as hibernate validator, this class will be automatically assembled (too new version can not be introduced, possibly because the javax related package path is modified to Jakarta, so this class cannot be assembled)
- The component LocalValidatorFactoryBean encapsulates Spring's registration of ValidatorFactory Validator and other components of the validation API, which is also the object to be expanded in this article
- MethodValidationPostProcessor is a key processing class that Spring can implement method parameter and result verification based on @ Validated annotation
AddValueExtractorLocalValidatorFactoryBean
public class AddValueExtractorLocalValidatorFactoryBean extends LocalValidatorFactoryBean { List<ValueExtractor> valueExtractors; public List<ValueExtractor> getValueExtractors() { return valueExtractors; } public void setValueExtractors(List<ValueExtractor> valueExtractors) { this.valueExtractors = valueExtractors; } @Override protected void postProcessConfiguration(Configuration<?> configuration) { // Add all custom valueextractors valueExtractors.forEach(configuration::addValueExtractor); } }
We extend the postProcessConfiguration method by inheriting the LocalValidatorFactoryBean class: allowing a set of valueextractors to be passed in and added to the Validation Configuration
ResultValueExtractor
@Component public class ResultValueExtractor implements ValueExtractor<Result<@ExtractedValue ?>> { @Override public void extractValues(Result<?> result, ValueReceiver valueReceiver) { valueReceiver.value(null, result.getData()); } }
Add a custom ValueExtractor and register it as a bean component to facilitate container collection
BeanValidationConfig
@Configuration public class BeanValidationConfig { @Bean public static LocalValidatorFactoryBean defaultValidator( ApplicationContext applicationContext , List<ValueExtractor> valueExtractors ) { // Registered here is our own extended AddValueExtractorLocalValidatorFactoryBean AddValueExtractorLocalValidatorFactoryBean factoryBean = new AddValueExtractorLocalValidatorFactoryBean(); factoryBean.setValueExtractors(valueExtractors); MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory(applicationContext); factoryBean.setMessageInterpolator(interpolatorFactory.getObject()); return factoryBean; } @Bean public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment, @Lazy Validator validator, ObjectProvider<MethodValidationExcludeFilter> excludeFilters) { FilteredMethodValidationPostProcessor processor = new FilteredMethodValidationPostProcessor( excludeFilters.orderedStream()); boolean proxyTargetClass = environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true); processor.setProxyTargetClass(proxyTargetClass); processor.setValidator(validator); return processor; } }
- This is actually a copy of the configuration in ValidationAutoConfiguration
- Of course, the localvalidator factorybean is registered with the addvalueextractorlocalvalidator factorybean that we have expanded
- All custom valueextractors in the container will be collected and added here
- At this point, we can verify the custom result set returned by the getResult method in the initial example
MethodValidationPostProcessor
After all, let's talk about the method validation postprocessor
- The above is its class inheritance diagram. MethodValidationPostProcessor is an implementation of the abstractadvising beanpostprocessor branch under ProxyProcessorSupport. This branch is proxy enhanced based on Advisor. Subclasses provide specific advisors, such as AsyncAnnotationBeanPostProcessor and MethodValidationPostProcessor
- At the same time, mention AbstractAutoProxyCreator, another branch of ProxyProcessorSupport. It proxies qualified bean components. Subclasses focus on obtaining qualified beans, such as the implementation of Spring AOP @Aspect
private Class<? extends Annotation> validatedAnnotationType = Validated.class; @Override public void afterPropertiesSet() { // Pointcut is an annotation matching pointcut based on the @ Validated annotation Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true); this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator)); } // Advice is the MethodValidationInterceptor protected Advice createMethodValidationAdvice(@Nullable Validator validator) { return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor()); }
- In popular understanding, Advisor = Pointcut + Advice. The former determines the entry point (i.e. specific method), and the latter corresponds to the notification logic (here corresponds to the parameter and return value verification of the method)
- The Pointcut of MethodValidationPostProcessor is an annotationmatching Pointcut based on the @ Validated annotation, which is why the @ Validated annotation is added to the verification class (provided by Spring)
- The Advice of the MethodValidationPostProcessor is the MethodValidationInterceptor, which is responsible for verifying the parameters and return values of the corresponding method
MethodValidationInterceptor
@Override @Nullable public Object invoke(MethodInvocation invocation) throws Throwable { // FactoryBean related methods skip if (isFactoryBeanMetadataMethod(invocation.getMethod())) { return invocation.proceed(); } Class<?>[] groups = determineValidationGroups(invocation); // The related validation depends on the ExecutableValidator API ExecutableValidator execVal = this.validator.forExecutables(); Method methodToValidate = invocation.getMethod(); Set<ConstraintViolation<Object>> result; Object target = invocation.getThis(); Assert.state(target != null, "Target must not be null"); try { // Parameter verification result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups); } catch (IllegalArgumentException ex) { // ... } // Verification failure throw exception if (!result.isEmpty()) { throw new ConstraintViolationException(result); } Object returnValue = invocation.proceed(); // Return value verification result = execVal.validateReturnValue(target, methodToValidate, returnValue, groups); // Verification failure throw exception if (!result.isEmpty()) { throw new ConstraintViolationException(result); } return returnValue; }
The specific notification logic is relatively simple. It depends on the ExecutableValidator to process the parameters and return values. If the verification fails, a ConstraintViolationException is thrown
summary
This paper mainly:
- It is extended based on the LocalValidatorFactoryBean provided by Spring to support the verification of custom result sets
- The MethodValidationPostProcessor is simply parsed to analyze Spring annotation based method parameters and return value verification
For the understanding of Advisor Pointcut Advice, etc., you can refer to the link
[source code] Spring AOP 2 Advice