Exclude property beans from method validation

Exclude `@ConfigurationProperties` beans from method validation so
that `@Validated` can be used on final classes without the method
validation post-processor throwing an exception.

This commit introduces a `FilteredMethodValidationPostProcessor` class
which will use `MethodValidationExcludeFilters` to exclude beans from
method validation processing. Using `@EnableConfigurationProperties`
will automatically register an appropriate filter.

Closes gh-21454
This commit is contained in:
Andy Wilkinson
2020-10-27 09:47:46 +00:00
committed by Phillip Webb
parent a0862f9146
commit f60f3cb38e
9 changed files with 288 additions and 7 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,12 +19,15 @@ package org.springframework.boot.autoconfigure.validation;
import javax.validation.Validator;
import javax.validation.executable.ExecutableValidator;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
import org.springframework.boot.validation.MessageInterpolatorFactory;
import org.springframework.boot.validation.beanvalidation.FilteredMethodValidationPostProcessor;
import org.springframework.boot.validation.beanvalidation.MethodValidationExcludeFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@@ -61,8 +64,9 @@ public class ValidationAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment,
@Lazy Validator validator) {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
@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);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfigurationTests.CustomValidatorConfiguration.TestBeanPostProcessor;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.validation.beanvalidation.MethodValidationExcludeFilter;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -43,6 +44,7 @@ import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBea
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.mockito.Mockito.mock;
/**
@@ -159,6 +161,15 @@ class ValidationAutoConfigurationTests {
assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(() -> service.doSomething("KO"));
}
@Test
void classCanBeExcludedFromValidation() {
load(ExcludedServiceConfiguration.class);
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
ExcludedService service = this.context.getBean(ExcludedService.class);
service.doSomething("Valid");
assertThatNoException().isThrownBy(() -> service.doSomething("KO"));
}
@Test
void validationUsesCglibProxy() {
load(DefaultAnotherSampleService.class);
@@ -285,6 +296,29 @@ class ValidationAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static final class ExcludedServiceConfiguration {
@Bean
ExcludedService excludedService() {
return new ExcludedService();
}
@Bean
MethodValidationExcludeFilter exclusionFilter() {
return (type) -> type.equals(ExcludedService.class);
}
}
@Validated
static final class ExcludedService {
void doSomething(@Size(min = 3, max = 10) String name) {
}
}
interface AnotherSampleService {
void doSomething(@Min(42) Integer counter);