Skip to content

Commit c9561f0

Browse files
committed
Refine validator and MVC validator configuration
Update `ValidationAutoConfiguration` and `WebMvcAutoConfiguration` to ensure as much as possible that only a single Validator bean of each type is registered. Validation auto-configuration now does the following: - If no validator is found: Registers a `LocalValidatorFactoryBean` (providing both Spring and JSR validation) - If the user defines a Spring & JSR validator: Backs off - If the user defines only a JSR validator: Adapts it to a Spring validator (without exposing another JSR implementation) WebMvcAutoConfiguration auto-configuration has been updated to make MVC validation follow common Spring Boot patterns: - If not validator beans are found (due to the user excluding ValidationAutoConfiguration) a new `mvcValidator` bean will be registered. - If a single validator bean is found it will be used for MVC validation. - If multiple validator beans are defined it will either use the one named `mvcValidator` or it will register a new `mvcValidator` bean Any automatically registered `mvcValidator` bean will not implement the JSR validator interface. Finally, it is no longer possible to provide an MVC validator via a `WebMvcConfigurer`. Fixes gh-8495
1 parent 2a7fd50 commit c9561f0

File tree

14 files changed

+774
-431
lines changed

14 files changed

+774
-431
lines changed

spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
4242
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
4343
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
44+
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
4445
import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration;
4546
import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
4647
import org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration;
@@ -156,9 +157,9 @@ protected TestInterceptor interceptor() {
156157
@Documented
157158
@Import({ EmbeddedServletContainerAutoConfiguration.class,
158159
ServerPropertiesAutoConfiguration.class,
159-
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
160-
JacksonAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
161-
PropertyPlaceholderAutoConfiguration.class })
160+
DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class,
161+
WebMvcAutoConfiguration.class, JacksonAutoConfiguration.class,
162+
ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
162163
protected @interface MinimalWebConfiguration {
163164

164165
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.validation;
18+
19+
import org.springframework.beans.factory.config.BeanDefinition;
20+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
21+
import org.springframework.boot.validation.MessageInterpolatorFactory;
22+
import org.springframework.context.annotation.Bean;
23+
import org.springframework.context.annotation.Configuration;
24+
import org.springframework.context.annotation.Role;
25+
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
26+
27+
/**
28+
* Default validator configuration imported by {@link ValidationAutoConfiguration}.
29+
*
30+
* @author Stephane Nicoll
31+
* @author Phillip Webb
32+
*/
33+
@Configuration
34+
class DefaultValidatorConfiguration {
35+
36+
@Bean
37+
@ConditionalOnMissingBean(type = { "javax.validation.Validator",
38+
"org.springframework.validation.Validator" })
39+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
40+
public static LocalValidatorFactoryBean defaultValidator() {
41+
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
42+
MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
43+
factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
44+
return factoryBean;
45+
}
46+
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.validation;
18+
19+
import org.springframework.util.Assert;
20+
import org.springframework.validation.Errors;
21+
import org.springframework.validation.SmartValidator;
22+
import org.springframework.validation.Validator;
23+
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
24+
25+
/**
26+
* {@link Validator} implementation that delegates calls to another {@link Validator}.
27+
* This {@link Validator} implements Spring's {@link SmartValidator} interface but does
28+
* not implement the JSR-303 {@code javax.validator.Validator} interface.
29+
*
30+
* @author Phillip Webb
31+
* @since 1.5.3
32+
*/
33+
public class DelegatingValidator implements SmartValidator {
34+
35+
private final Validator delegate;
36+
37+
/**
38+
* Create a new {@link DelegatingValidator} instance.
39+
* @param targetValidator the target JSR validator
40+
*/
41+
public DelegatingValidator(javax.validation.Validator targetValidator) {
42+
this.delegate = new SpringValidatorAdapter(targetValidator);
43+
}
44+
45+
/**
46+
* Create a new {@link DelegatingValidator} instance.
47+
* @param targetValidator the target validator
48+
*/
49+
public DelegatingValidator(Validator targetValidator) {
50+
Assert.notNull(targetValidator, "Target Validator must not be null");
51+
this.delegate = targetValidator;
52+
}
53+
54+
@Override
55+
public boolean supports(Class<?> clazz) {
56+
return this.delegate.supports(clazz);
57+
}
58+
59+
@Override
60+
public void validate(Object target, Errors errors) {
61+
this.delegate.validate(target, errors);
62+
}
63+
64+
@Override
65+
public void validate(Object target, Errors errors, Object... validationHints) {
66+
if (this.delegate instanceof SmartValidator) {
67+
((SmartValidator) this.delegate).validate(target, errors, validationHints);
68+
}
69+
else {
70+
this.delegate.validate(target, errors);
71+
}
72+
}
73+
74+
protected final Validator getDelegate() {
75+
return this.delegate;
76+
}
77+
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.validation;
18+
19+
import javax.validation.Validator;
20+
21+
import org.springframework.beans.factory.config.BeanDefinition;
22+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
23+
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
24+
import org.springframework.context.annotation.Bean;
25+
import org.springframework.context.annotation.Configuration;
26+
import org.springframework.context.annotation.Role;
27+
import org.springframework.validation.SmartValidator;
28+
29+
/**
30+
* JSR 303 adapter configration imported by {@link ValidationAutoConfiguration}.
31+
*
32+
* @author Stephane Nicoll
33+
* @author Phillip Webb
34+
*/
35+
@Configuration
36+
class Jsr303ValidatorAdapterConfiguration {
37+
38+
@Bean
39+
@ConditionalOnSingleCandidate(Validator.class)
40+
@ConditionalOnMissingBean(org.springframework.validation.Validator.class)
41+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
42+
public SmartValidator jsr303ValidatorAdapter(Validator validator) {
43+
return new DelegatingValidator(validator);
44+
}
45+
46+
}

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.java

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,16 @@
1919
import javax.validation.Validator;
2020
import javax.validation.executable.ExecutableValidator;
2121

22-
import org.springframework.beans.factory.config.BeanDefinition;
2322
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
23+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2424
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2525
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2626
import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
2727
import org.springframework.boot.bind.RelaxedPropertyResolver;
28-
import org.springframework.boot.validation.MessageInterpolatorFactory;
2928
import org.springframework.context.annotation.Bean;
3029
import org.springframework.context.annotation.Configuration;
31-
import org.springframework.context.annotation.Role;
30+
import org.springframework.context.annotation.Import;
3231
import org.springframework.core.env.Environment;
33-
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
3432
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
3533

3634
/**
@@ -43,19 +41,12 @@
4341
@Configuration
4442
@ConditionalOnClass(ExecutableValidator.class)
4543
@ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider")
44+
@Import({ DefaultValidatorConfiguration.class,
45+
Jsr303ValidatorAdapterConfiguration.class })
4646
public class ValidationAutoConfiguration {
4747

4848
@Bean
49-
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
50-
@ConditionalOnMissingBean
51-
public static Validator jsr303Validator() {
52-
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
53-
MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
54-
factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
55-
return factoryBean;
56-
}
57-
58-
@Bean
49+
@ConditionalOnBean(Validator.class)
5950
@ConditionalOnMissingBean
6051
public static MethodValidationPostProcessor methodValidationPostProcessor(
6152
Environment environment, Validator validator) {

0 commit comments

Comments
 (0)