Merge branch '1.5.x'

This commit is contained in:
Phillip Webb 2017-04-19 22:48:19 -07:00
commit 81fef71fcb
30 changed files with 1401 additions and 572 deletions

View File

@ -42,6 +42,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
@ -85,15 +86,16 @@ public class EndpointMvcIntegrationTests {
@Test
public void envEndpointNotHidden() throws InterruptedException {
String body = new TestRestTemplate().getForObject(
"http://localhost:" + this.port + "/application/env/foo.bar", String.class);
"http://localhost:" + this.port + "/application/env/foo.bar",
String.class);
assertThat(body).isNotNull().contains("\"baz\"");
assertThat(this.interceptor.invoked()).isTrue();
}
@Test
public void healthEndpointNotHidden() throws InterruptedException {
String body = new TestRestTemplate()
.getForObject("http://localhost:" + this.port + "/application/health", String.class);
String body = new TestRestTemplate().getForObject(
"http://localhost:" + this.port + "/application/health", String.class);
assertThat(body).isNotNull().contains("status");
assertThat(this.interceptor.invoked()).isTrue();
}
@ -153,9 +155,9 @@ public class EndpointMvcIntegrationTests {
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ ServletWebServerFactoryAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
JacksonAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class,
WebMvcAutoConfiguration.class, JacksonAutoConfiguration.class,
ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
protected @interface MinimalWebConfiguration {
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2012-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.validation;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.validation.MessageInterpolatorFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
/**
* Default validator configuration imported by {@link ValidationAutoConfiguration}.
*
* @author Stephane Nicoll
* @author Phillip Webb
*/
@Configuration
class DefaultValidatorConfiguration {
@Bean
@ConditionalOnMissingBean(type = { "javax.validation.Validator",
"org.springframework.validation.Validator" })
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public static LocalValidatorFactoryBean defaultValidator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
return factoryBean;
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 2012-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.validation;
import org.springframework.util.Assert;
import org.springframework.validation.Errors;
import org.springframework.validation.SmartValidator;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
/**
* {@link Validator} implementation that delegates calls to another {@link Validator}.
* This {@link Validator} implements Spring's {@link SmartValidator} interface but does
* not implement the JSR-303 {@code javax.validator.Validator} interface.
*
* @author Phillip Webb
* @since 1.5.3
*/
public class DelegatingValidator implements SmartValidator {
private final Validator delegate;
/**
* Create a new {@link DelegatingValidator} instance.
* @param targetValidator the target JSR validator
*/
public DelegatingValidator(javax.validation.Validator targetValidator) {
this.delegate = new SpringValidatorAdapter(targetValidator);
}
/**
* Create a new {@link DelegatingValidator} instance.
* @param targetValidator the target validator
*/
public DelegatingValidator(Validator targetValidator) {
Assert.notNull(targetValidator, "Target Validator must not be null");
this.delegate = targetValidator;
}
@Override
public boolean supports(Class<?> clazz) {
return this.delegate.supports(clazz);
}
@Override
public void validate(Object target, Errors errors) {
this.delegate.validate(target, errors);
}
@Override
public void validate(Object target, Errors errors, Object... validationHints) {
if (this.delegate instanceof SmartValidator) {
((SmartValidator) this.delegate).validate(target, errors, validationHints);
}
else {
this.delegate.validate(target, errors);
}
}
/**
* Return the delegate validator.
* @return the delegate validator
*/
protected final Validator getDelegate() {
return this.delegate;
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2012-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.validation;
import javax.validation.Validator;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.validation.SmartValidator;
/**
* JSR 303 adapter configration imported by {@link ValidationAutoConfiguration}.
*
* @author Stephane Nicoll
* @author Phillip Webb
*/
@Configuration
class Jsr303ValidatorAdapterConfiguration {
@Bean
@ConditionalOnSingleCandidate(Validator.class)
@ConditionalOnMissingBean(org.springframework.validation.Validator.class)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public SmartValidator jsr303ValidatorAdapter(Validator validator) {
return new DelegatingValidator(validator);
}
}

View File

@ -1,145 +0,0 @@
/*
* Copyright 2012-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.validation;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.validation.MessageInterpolatorFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.validation.Errors;
import org.springframework.validation.SmartValidator;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
/**
* A {@link SmartValidator} exposed as a bean for WebMvc use. Wraps existing
* {@link SpringValidatorAdapter} instances so that only the Spring's {@link Validator}
* type is exposed. This prevents such a bean to expose both the Spring and JSR-303
* validator contract at the same time.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 2.0.0
*/
public class SpringValidator implements SmartValidator, ApplicationContextAware,
InitializingBean, DisposableBean {
private final SpringValidatorAdapter target;
private final boolean existingBean;
public SpringValidator(SpringValidatorAdapter target, boolean existingBean) {
this.target = target;
this.existingBean = existingBean;
}
public final SpringValidatorAdapter getTarget() {
return this.target;
}
@Override
public boolean supports(Class<?> clazz) {
return this.target.supports(clazz);
}
@Override
public void validate(Object target, Errors errors) {
this.target.validate(target, errors);
}
@Override
public void validate(Object target, Errors errors, Object... validationHints) {
this.target.validate(target, errors, validationHints);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
if (!this.existingBean && this.target instanceof ApplicationContextAware) {
((ApplicationContextAware) this.target)
.setApplicationContext(applicationContext);
}
}
@Override
public void afterPropertiesSet() throws Exception {
if (!this.existingBean && this.target instanceof InitializingBean) {
((InitializingBean) this.target).afterPropertiesSet();
}
}
@Override
public void destroy() throws Exception {
if (!this.existingBean && this.target instanceof DisposableBean) {
((DisposableBean) this.target).destroy();
}
}
public static Validator get(ApplicationContext applicationContext,
Validator validator) {
if (validator != null) {
return wrap(validator, false);
}
return getExistingOrCreate(applicationContext);
}
private static Validator getExistingOrCreate(ApplicationContext applicationContext) {
Validator existing = getExisting(applicationContext);
if (existing != null) {
return wrap(existing, true);
}
return create();
}
private static Validator getExisting(ApplicationContext applicationContext) {
try {
javax.validation.Validator validator = applicationContext
.getBean(javax.validation.Validator.class);
if (validator instanceof Validator) {
return (Validator) validator;
}
return new SpringValidatorAdapter(validator);
}
catch (NoSuchBeanDefinitionException ex) {
return null;
}
}
private static Validator create() {
OptionalValidatorFactoryBean validator = new OptionalValidatorFactoryBean();
validator.setMessageInterpolator(new MessageInterpolatorFactory().getObject());
return wrap(validator, false);
}
private static Validator wrap(Validator validator, boolean existingBean) {
if (validator instanceof javax.validation.Validator) {
if (validator instanceof SpringValidatorAdapter) {
return new SpringValidator((SpringValidatorAdapter) validator,
existingBean);
}
return new SpringValidator(
new SpringValidatorAdapter((javax.validation.Validator) validator),
existingBean);
}
return validator;
}
}

View File

@ -19,18 +19,16 @@ package org.springframework.boot.autoconfigure.validation;
import javax.validation.Validator;
import javax.validation.executable.ExecutableValidator;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
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.bind.RelaxedPropertyResolver;
import org.springframework.boot.validation.MessageInterpolatorFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
/**
@ -43,19 +41,12 @@ import org.springframework.validation.beanvalidation.MethodValidationPostProcess
@Configuration
@ConditionalOnClass(ExecutableValidator.class)
@ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider")
@Import({ DefaultValidatorConfiguration.class,
Jsr303ValidatorAdapterConfiguration.class })
public class ValidationAutoConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ConditionalOnMissingBean
public static Validator jsr303Validator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
return factoryBean;
}
@Bean
@ConditionalOnBean(Validator.class)
@ConditionalOnMissingBean
public static MethodValidationPostProcessor methodValidationPostProcessor(
Environment environment, Validator validator) {

View File

@ -23,30 +23,50 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
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.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.validation.SpringValidator;
import org.springframework.boot.autoconfigure.validation.DelegatingValidator;
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Role;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.Order;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.CacheControl;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.Validator;
import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration;
import org.springframework.web.reactive.config.EnableWebFlux;
@ -82,6 +102,12 @@ import org.springframework.web.reactive.result.view.ViewResolver;
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class WebFluxAnnotationAutoConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public static WebFluxValidatorPostProcessor mvcValidatorAliasPostProcessor() {
return new WebFluxValidatorPostProcessor();
}
@Configuration
@EnableConfigurationProperties({ ResourceProperties.class, WebFluxProperties.class })
@Import(EnableWebFluxConfiguration.class)
@ -190,17 +216,29 @@ public class WebFluxAnnotationAutoConfiguration {
* Configuration equivalent to {@code @EnableWebFlux}.
*/
@Configuration
public static class EnableWebFluxConfiguration
extends DelegatingWebFluxConfiguration {
public static class EnableWebFluxConfiguration extends DelegatingWebFluxConfiguration
implements InitializingBean {
private final ApplicationContext context;
public EnableWebFluxConfiguration(ApplicationContext context) {
this.context = context;
}
@Bean
@Override
@Conditional(DisableWebFluxValidatorCondition.class)
public Validator webFluxValidator() {
return this.context.getBean("webFluxValidator", Validator.class);
}
@Override
@Bean
public Validator webFluxValidator() {
if (!ClassUtils.isPresent("javax.validation.Validator",
getClass().getClassLoader())) {
return super.webFluxValidator();
}
return SpringValidator.get(getApplicationContext(), getValidator());
public void afterPropertiesSet() throws Exception {
Assert.state(getValidator() == null,
"Found unexpected validator configuration. A Spring Boot WebFlux "
+ "validator should be registered as bean named "
+ "'webFluxValidator' and not returned from "
+ "WebFluxConfigurer.getValidator()");
}
}
@ -266,4 +304,128 @@ public class WebFluxAnnotationAutoConfiguration {
}
/**
* Condition used to disable the default WebFlux validator registration. The
* {@link WebFluxValidatorPostProcessor} is used to configure the
* {@code webFluxValidator} bean.
*/
static class DisableWebFluxValidatorCondition implements ConfigurationCondition {
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
/**
* {@link BeanFactoryPostProcessor} to deal with the MVC validator bean registration.
* Applies the following rules:
* <ul>
* <li>With no validators - Uses standard
* {@link WebFluxConfigurationSupport#webFluxValidator()} logic.</li>
* <li>With a single validator - Uses an alias.</li>
* <li>With multiple validators - Registers a mvcValidator bean if not already
* defined.</li>
* </ul>
*/
@Order(Ordered.LOWEST_PRECEDENCE)
static class WebFluxValidatorPostProcessor
implements BeanDefinitionRegistryPostProcessor {
private static final String JSR303_VALIDATOR_CLASS = "javax.validation.Validator";
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
if (registry instanceof ListableBeanFactory) {
postProcess(registry, (ListableBeanFactory) registry);
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
}
private void postProcess(BeanDefinitionRegistry registry,
ListableBeanFactory beanFactory) {
String[] validatorBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
beanFactory, Validator.class, false, false);
if (validatorBeans.length == 0) {
registerMvcValidator(registry, beanFactory);
}
else if (validatorBeans.length == 1) {
registry.registerAlias(validatorBeans[0], "webFluxValidator");
}
else {
if (!ObjectUtils.containsElement(validatorBeans, "webFluxValidator")) {
registerMvcValidator(registry, beanFactory);
}
}
}
private void registerMvcValidator(BeanDefinitionRegistry registry,
ListableBeanFactory beanFactory) {
RootBeanDefinition definition = new RootBeanDefinition();
definition.setBeanClass(getClass());
definition.setFactoryMethodName("webFluxValidator");
registry.registerBeanDefinition("webFluxValidator", definition);
}
static Validator webFluxValidator() {
Validator validator = new WebFluxConfigurationSupport().webFluxValidator();
try {
if (ClassUtils.forName(JSR303_VALIDATOR_CLASS, null)
.isInstance(validator)) {
return new DelegatingWebFluxValidator(validator);
}
}
catch (Exception ex) {
}
return validator;
}
}
/**
* {@link DelegatingValidator} for the WebFlux validator.
*/
static class DelegatingWebFluxValidator extends DelegatingValidator
implements ApplicationContextAware, InitializingBean, DisposableBean {
DelegatingWebFluxValidator(Validator targetValidator) {
super(targetValidator);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
if (getDelegate() instanceof ApplicationContextAware) {
((ApplicationContextAware) getDelegate())
.setApplicationContext(applicationContext);
}
}
@Override
public void afterPropertiesSet() throws Exception {
if (getDelegate() instanceof InitializingBean) {
((InitializingBean) getDelegate()).afterPropertiesSet();
}
}
@Override
public void destroy() throws Exception {
if (getDelegate() instanceof DisposableBean) {
((DisposableBean) getDelegate()).destroy();
}
}
}
}

View File

@ -30,11 +30,21 @@ import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@ -45,7 +55,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.autoconfigure.validation.SpringValidator;
import org.springframework.boot.autoconfigure.validation.DelegatingValidator;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
@ -54,21 +64,31 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.web.servlet.filter.OrderedHiddenHttpMethodFilter;
import org.springframework.boot.web.servlet.filter.OrderedHttpPutFormContentFilter;
import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Role;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.datetime.DateFormatter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.DefaultMessageCodesResolver;
import org.springframework.validation.MessageCodesResolver;
@ -147,6 +167,12 @@ public class WebMvcAutoConfiguration {
public static final String SKIP_PATH_EXTENSION_CONTENT_NEGOTIATION_ATTRIBUTE = PathExtensionContentNegotiationStrategy.class
.getName() + ".SKIP";
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public static MvcValidatorPostProcessor mvcValidatorAliasPostProcessor() {
return new MvcValidatorPostProcessor();
}
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
@ -372,21 +398,22 @@ public class WebMvcAutoConfiguration {
* Configuration equivalent to {@code @EnableWebMvc}.
*/
@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration
implements InitializingBean {
private final WebMvcProperties mvcProperties;
private final ListableBeanFactory beanFactory;
private final ApplicationContext context;
private final WebMvcRegistrations mvcRegistrations;
public EnableWebMvcConfiguration(
ObjectProvider<WebMvcProperties> mvcPropertiesProvider,
ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider,
ListableBeanFactory beanFactory) {
ApplicationContext context) {
this.mvcProperties = mvcPropertiesProvider.getIfAvailable();
this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
this.beanFactory = beanFactory;
this.context = context;
}
@Bean
@ -417,12 +444,9 @@ public class WebMvcAutoConfiguration {
@Bean
@Override
@Conditional(DisableMvcValidatorCondition.class)
public Validator mvcValidator() {
if (!ClassUtils.isPresent("javax.validation.Validator",
getClass().getClassLoader())) {
return super.mvcValidator();
}
return SpringValidator.get(getApplicationContext(), getValidator());
return this.context.getBean("mvcValidator", Validator.class);
}
@Override
@ -437,7 +461,7 @@ public class WebMvcAutoConfiguration {
@Override
protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() {
try {
return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class);
return this.context.getBean(ConfigurableWebBindingInitializer.class);
}
catch (NoSuchBeanDefinitionException ex) {
return super.getConfigurableWebBindingInitializer();
@ -486,6 +510,15 @@ public class WebMvcAutoConfiguration {
return manager;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.state(getValidator() == null,
"Found unexpected validator configuration. A Spring Boot MVC "
+ "validator should be registered as bean named "
+ "'mvcValidator' and not returned from "
+ "WebMvcConfigurer.getValidator()");
}
}
@Configuration
@ -611,4 +644,128 @@ public class WebMvcAutoConfiguration {
}
/**
* Condition used to disable the default MVC validator registration. The
* {@link MvcValidatorPostProcessor} is used to configure the {@code mvcValidator}
* bean.
*/
static class DisableMvcValidatorCondition implements ConfigurationCondition {
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
/**
* {@link BeanFactoryPostProcessor} to deal with the MVC validator bean registration.
* Applies the following rules:
* <ul>
* <li>With no validators - Uses standard
* {@link WebMvcConfigurationSupport#mvcValidator()} logic.</li>
* <li>With a single validator - Uses an alias.</li>
* <li>With multiple validators - Registers a mvcValidator bean if not already
* defined.</li>
* </ul>
*/
@Order(Ordered.LOWEST_PRECEDENCE)
static class MvcValidatorPostProcessor
implements BeanDefinitionRegistryPostProcessor {
private static final String JSR303_VALIDATOR_CLASS = "javax.validation.Validator";
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
if (registry instanceof ListableBeanFactory) {
postProcess(registry, (ListableBeanFactory) registry);
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
}
private void postProcess(BeanDefinitionRegistry registry,
ListableBeanFactory beanFactory) {
String[] validatorBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
beanFactory, Validator.class, false, false);
if (validatorBeans.length == 0) {
registerMvcValidator(registry, beanFactory);
}
else if (validatorBeans.length == 1) {
registry.registerAlias(validatorBeans[0], "mvcValidator");
}
else {
if (!ObjectUtils.containsElement(validatorBeans, "mvcValidator")) {
registerMvcValidator(registry, beanFactory);
}
}
}
private void registerMvcValidator(BeanDefinitionRegistry registry,
ListableBeanFactory beanFactory) {
RootBeanDefinition definition = new RootBeanDefinition();
definition.setBeanClass(getClass());
definition.setFactoryMethodName("mvcValidator");
registry.registerBeanDefinition("mvcValidator", definition);
}
static Validator mvcValidator() {
Validator validator = new WebMvcConfigurationSupport().mvcValidator();
try {
if (ClassUtils.forName(JSR303_VALIDATOR_CLASS, null)
.isInstance(validator)) {
return new DelegatingWebMvcValidator(validator);
}
}
catch (Exception ex) {
}
return validator;
}
}
/**
* {@link DelegatingValidator} for the MVC validator.
*/
static class DelegatingWebMvcValidator extends DelegatingValidator
implements ApplicationContextAware, InitializingBean, DisposableBean {
DelegatingWebMvcValidator(Validator targetValidator) {
super(targetValidator);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
if (getDelegate() instanceof ApplicationContextAware) {
((ApplicationContextAware) getDelegate())
.setApplicationContext(applicationContext);
}
}
@Override
public void afterPropertiesSet() throws Exception {
if (getDelegate() instanceof InitializingBean) {
((InitializingBean) getDelegate()).afterPropertiesSet();
}
}
@Override
public void destroy() throws Exception {
if (getDelegate() instanceof DisposableBean) {
((DisposableBean) getDelegate()).destroy();
}
}
}
}

View File

@ -32,6 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
@ -322,9 +323,9 @@ public class SpringBootWebSecurityConfigurationTests {
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ ServletWebServerFactoryAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class,
WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
protected @interface MinimalWebConfiguration {
}

View File

@ -0,0 +1,116 @@
/*
* Copyright 2012-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.validation;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.SmartValidator;
import org.springframework.validation.Validator;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link DelegatingValidator}.
*
* @author Phillip Webb
*/
public class DelegatingValidatorTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Mock
private SmartValidator delegate;
private DelegatingValidator delegating;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.delegating = new DelegatingValidator(this.delegate);
}
@Test
public void createWhenJsrValidatorIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Target Validator must not be null");
new DelegatingValidator((javax.validation.Validator) null);
}
@Test
public void createWithJsrValidatorShouldAdapt() throws Exception {
javax.validation.Validator delegate = mock(javax.validation.Validator.class);
Validator delegating = new DelegatingValidator(delegate);
Object target = new Object();
Errors errors = new BeanPropertyBindingResult(target, "foo");
delegating.validate(target, errors);
verify(delegate).validate(any());
}
@Test
public void createWithSpringValidatorWhenValidatorIsNullShouldThrowException()
throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Target Validator must not be null");
new DelegatingValidator((Validator) null);
}
@Test
public void supportsShouldDelegateToValidator() throws Exception {
this.delegating.supports(Object.class);
verify(this.delegate).supports(Object.class);
}
@Test
public void validateShouldDelegateToValidator() throws Exception {
Object target = new Object();
Errors errors = new BeanPropertyBindingResult(target, "foo");
this.delegating.validate(target, errors);
verify(this.delegate).validate(target, errors);
}
@Test
public void validateWithHintsShouldDelegateToValidator() throws Exception {
Object target = new Object();
Errors errors = new BeanPropertyBindingResult(target, "foo");
Object[] hints = { "foo", "bar" };
this.delegating.validate(target, errors, hints);
verify(this.delegate).validate(target, errors, hints);
}
@Test
public void validateWithHintsWhenDelegateIsNotSmartShouldDelegateToSimpleValidator()
throws Exception {
Validator delegate = mock(Validator.class);
DelegatingValidator delegating = new DelegatingValidator(delegate);
Object target = new Object();
Errors errors = new BeanPropertyBindingResult(target, "foo");
Object[] hints = { "foo", "bar" };
delegating.validate(target, errors, hints);
verify(delegate).validate(target, errors);
}
}

View File

@ -1,151 +0,0 @@
/*
* Copyright 2012-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.validation;
import java.util.HashMap;
import javax.validation.constraints.Min;
import org.junit.After;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.MapBindingResult;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link SpringValidator}.
*
* @author Stephane Nicoll
*/
public class SpringValidatorTests {
private AnnotationConfigApplicationContext context;
@After
public void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void wrapLocalValidatorFactoryBean() {
SpringValidator wrapper = load(LocalValidatorFactoryBeanConfig.class);
assertThat(wrapper.supports(SampleData.class)).isTrue();
MapBindingResult errors = new MapBindingResult(new HashMap<String, Object>(),
"test");
wrapper.validate(new SampleData(40), errors);
assertThat(errors.getErrorCount()).isEqualTo(1);
}
@Test
public void wrapperInvokesCallbackOnNonManagedBean() {
load(NonManagedBeanConfig.class);
LocalValidatorFactoryBean validator = this.context
.getBean(NonManagedBeanConfig.class).validator;
verify(validator, times(1)).setApplicationContext(any(ApplicationContext.class));
verify(validator, times(1)).afterPropertiesSet();
verify(validator, times(0)).destroy();
this.context.close();
this.context = null;
verify(validator, times(1)).destroy();
}
@Test
public void wrapperDoesNotInvokeCallbackOnManagedBean() {
load(ManagedBeanConfig.class);
LocalValidatorFactoryBean validator = this.context
.getBean(ManagedBeanConfig.class).validator;
verify(validator, times(0)).setApplicationContext(any(ApplicationContext.class));
verify(validator, times(0)).afterPropertiesSet();
verify(validator, times(0)).destroy();
this.context.close();
this.context = null;
verify(validator, times(0)).destroy();
}
private SpringValidator load(Class<?> config) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(config);
ctx.refresh();
this.context = ctx;
return this.context.getBean(SpringValidator.class);
}
@Configuration
static class LocalValidatorFactoryBeanConfig {
@Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
@Bean
public SpringValidator wrapper() {
return new SpringValidator(validator(), true);
}
}
@Configuration
static class NonManagedBeanConfig {
private final LocalValidatorFactoryBean validator = mock(
LocalValidatorFactoryBean.class);
@Bean
public SpringValidator wrapper() {
return new SpringValidator(this.validator, false);
}
}
@Configuration
static class ManagedBeanConfig {
private final LocalValidatorFactoryBean validator = mock(
LocalValidatorFactoryBean.class);
@Bean
public SpringValidator wrapper() {
return new SpringValidator(this.validator, true);
}
}
static class SampleData {
@Min(42)
private int counter;
SampleData(int counter) {
this.counter = counter;
}
}
}

View File

@ -32,9 +32,12 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ValidationAutoConfiguration}.
@ -56,45 +59,94 @@ public class ValidationAutoConfigurationTests {
}
@Test
public void validationIsEnabled() {
load(SampleService.class);
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
SampleService service = this.context.getBean(SampleService.class);
service.doSomething("Valid");
this.thrown.expect(ConstraintViolationException.class);
service.doSomething("KO");
public void validationAutoConfigurationShouldConfigureJsrAndSpringValidator()
throws Exception {
load(Config.class);
Validator jsrValidator = this.context.getBean(Validator.class);
String[] jsrValidatorNames = this.context.getBeanNamesForType(Validator.class);
org.springframework.validation.Validator springValidator = this.context
.getBean(org.springframework.validation.Validator.class);
String[] springValidatorNames = this.context
.getBeanNamesForType(org.springframework.validation.Validator.class);
assertThat(jsrValidator).isInstanceOf(LocalValidatorFactoryBean.class);
assertThat(jsrValidator).isEqualTo(springValidator);
assertThat(jsrValidatorNames).containsExactly("defaultValidator");
assertThat(springValidatorNames).containsExactly("defaultValidator");
}
@Test
public void validationUsesCglibProxy() {
load(DefaultAnotherSampleService.class);
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
DefaultAnotherSampleService service = this.context
.getBean(DefaultAnotherSampleService.class);
service.doSomething(42);
this.thrown.expect(ConstraintViolationException.class);
service.doSomething(2);
public void validationAutoConfigurationWhenUserProvidesValidatorShouldBackOff()
throws Exception {
load(UserDefinedValidatorConfig.class);
Validator jsrValidator = this.context.getBean(Validator.class);
String[] jsrValidatorNames = this.context.getBeanNamesForType(Validator.class);
org.springframework.validation.Validator springValidator = this.context
.getBean(org.springframework.validation.Validator.class);
String[] springValidatorNames = this.context
.getBeanNamesForType(org.springframework.validation.Validator.class);
assertThat(jsrValidator).isInstanceOf(OptionalValidatorFactoryBean.class);
assertThat(jsrValidator).isEqualTo(springValidator);
assertThat(jsrValidatorNames).containsExactly("customValidator");
assertThat(springValidatorNames).containsExactly("customValidator");
}
@Test
public void validationCanBeConfiguredToUseJdkProxy() {
public void validationAutoConfigurationWhenUserProvidesJsrOnlyShouldAdaptIt()
throws Exception {
load(UserDefinedJsrValidatorConfig.class);
Validator jsrValidator = this.context.getBean(Validator.class);
String[] jsrValidatorNames = this.context.getBeanNamesForType(Validator.class);
org.springframework.validation.Validator springValidator = this.context
.getBean(org.springframework.validation.Validator.class);
String[] springValidatorNames = this.context
.getBeanNamesForType(org.springframework.validation.Validator.class);
assertThat(jsrValidator).isNotEqualTo(springValidator);
assertThat(springValidator).isInstanceOf(DelegatingValidator.class);
assertThat(jsrValidatorNames).containsExactly("customValidator");
assertThat(springValidatorNames).containsExactly("jsr303ValidatorAdapter");
}
@Test
public void validationAutoConfigurationShouldBeEnabled() {
load(ClassWithConstraint.class);
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
ClassWithConstraint service = this.context.getBean(ClassWithConstraint.class);
service.call("Valid");
this.thrown.expect(ConstraintViolationException.class);
service.call("KO");
}
@Test
public void validationAutoConfigurationShouldUseCglibProxy() {
load(ImplementationOfInterfaceWithConstraint.class);
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
ImplementationOfInterfaceWithConstraint service = this.context
.getBean(ImplementationOfInterfaceWithConstraint.class);
service.call(42);
this.thrown.expect(ConstraintViolationException.class);
service.call(2);
}
@Test
public void validationAutoConfigurationWhenProxyTargetClassIsFalseShouldUseJdkProxy() {
load(AnotherSampleServiceConfiguration.class,
"spring.aop.proxy-target-class=false");
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
assertThat(this.context.getBeansOfType(DefaultAnotherSampleService.class))
.isEmpty();
AnotherSampleService service = this.context.getBean(AnotherSampleService.class);
service.doSomething(42);
assertThat(this.context
.getBeansOfType(ImplementationOfInterfaceWithConstraint.class)).isEmpty();
InterfaceWithConstraint service = this.context
.getBean(InterfaceWithConstraint.class);
service.call(42);
this.thrown.expect(ConstraintViolationException.class);
service.doSomething(2);
service.call(2);
}
@Test
public void userDefinedMethodValidationPostProcessorTakesPrecedence() {
load(SampleConfiguration.class);
public void validationAutoConfigurationWhenUserDefinesMethodValidationPostProcessorShouldBackOff() {
load(UserDefinedMethodValidationConfig.class);
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
Object userMethodValidationPostProcessor = this.context
.getBean("testMethodValidationPostProcessor");
.getBean("customMethodValidationPostProcessor");
assertThat(this.context.getBean(MethodValidationPostProcessor.class))
.isSameAs(userMethodValidationPostProcessor);
assertThat(this.context.getBeansOfType(MethodValidationPostProcessor.class))
@ -115,47 +167,73 @@ public class ValidationAutoConfigurationTests {
this.context = ctx;
}
@Validated
static class SampleService {
@Configuration
static class Config {
public void doSomething(@Size(min = 3, max = 10) String name) {
}
@Configuration
static class UserDefinedValidatorConfig {
@Bean
public OptionalValidatorFactoryBean customValidator() {
return new OptionalValidatorFactoryBean();
}
}
interface AnotherSampleService {
@Configuration
static class UserDefinedJsrValidatorConfig {
@Bean
public Validator customValidator() {
return mock(Validator.class);
}
void doSomething(@Min(42) Integer counter);
}
@Validated
static class DefaultAnotherSampleService implements AnotherSampleService {
@Override
public void doSomething(Integer counter) {
@Configuration
static class UserDefinedMethodValidationConfig {
@Bean
public MethodValidationPostProcessor customMethodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
@Configuration
static class AnotherSampleServiceConfiguration {
@Bean
public AnotherSampleService anotherSampleService() {
return new DefaultAnotherSampleService();
public InterfaceWithConstraint implementationOfInterfaceWithConstraint() {
return new ImplementationOfInterfaceWithConstraint();
}
}
@Configuration
static class SampleConfiguration {
@Validated
static class ClassWithConstraint {
public void call(@Size(min = 3, max = 10) String name) {
@Bean
public MethodValidationPostProcessor testMethodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
interface InterfaceWithConstraint {
void call(@Min(42) Integer counter);
}
@Validated
static class ImplementationOfInterfaceWithConstraint
implements InterfaceWithConstraint {
@Override
public void call(Integer counter) {
}
}
}

View File

@ -16,15 +16,19 @@
package org.springframework.boot.autoconfigure.web.reactive;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import javax.validation.ValidatorFactory;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.boot.autoconfigure.validation.SpringValidator;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.autoconfigure.validation.DelegatingValidator;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfigurationTests.Config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext;
@ -36,9 +40,9 @@ import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.accept.CompositeContentTypeResolver;
import org.springframework.web.reactive.config.WebFluxConfigurationSupport;
@ -65,6 +69,9 @@ import static org.mockito.Mockito.mock;
*/
public class WebFluxAnnotationAutoConfigurationTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private GenericReactiveWebApplicationContext context;
@Test
@ -165,64 +172,134 @@ public class WebFluxAnnotationAutoConfigurationTests {
}
@Test
public void validationNoJsr303ValidatorExposedByDefault() {
public void validatorWhenSuppliedByConfigurerShouldThrowException() throws Exception {
this.thrown.expect(BeanCreationException.class);
this.thrown.expectMessage("unexpected validator configuration");
load(ValidatorWebFluxConfigurer.class);
}
@Test
public void validatorWhenAutoConfiguredShouldUseAlias() throws Exception {
load();
assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
.isEmpty();
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
Object defaultValidator = this.context.getBean("defaultValidator");
Object webFluxValidator = this.context.getBean("webFluxValidator");
String[] jsrValidatorBeans = this.context
.getBeanNamesForType(javax.validation.Validator.class);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(webFluxValidator).isSameAs(defaultValidator);
assertThat(springValidatorBeans).containsExactly("defaultValidator");
assertThat(jsrValidatorBeans).containsExactly("defaultValidator");
}
@Test
public void validationCustomConfigurerTakesPrecedence() {
load(WebFluxValidator.class);
assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
.isEmpty();
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
Validator validator = this.context.getBean(Validator.class);
assertThat(validator)
.isSameAs(this.context.getBean(WebFluxValidator.class).validator);
public void validatorWhenUserDefinedSpringOnlyShouldUseDefined() throws Exception {
load(UserDefinedSpringOnlyValidator.class);
Object customValidator = this.context.getBean("customValidator");
Object webFluxValidator = this.context.getBean("webFluxValidator");
String[] jsrValidatorBeans = this.context
.getBeanNamesForType(javax.validation.Validator.class);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(webFluxValidator).isSameAs(customValidator);
assertThat(this.context.getBean(Validator.class)).isEqualTo(customValidator);
assertThat(springValidatorBeans).containsExactly("customValidator");
assertThat(jsrValidatorBeans).isEmpty();
}
@Test
public void validationCustomConfigurerTakesPrecedenceAndDoNotExposeJsr303() {
load(WebFluxJsr303Validator.class);
assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
.isEmpty();
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
Validator validator = this.context.getBean(Validator.class);
assertThat(validator).isInstanceOf(SpringValidator.class);
assertThat(((SpringValidator) validator).getTarget())
.isSameAs(this.context.getBean(WebFluxJsr303Validator.class).validator);
public void validatorWhenUserDefinedJsr303ShouldAdapt() throws Exception {
load(UserDefinedJsr303Validator.class);
Object customValidator = this.context.getBean("customValidator");
Object webFluxValidator = this.context.getBean("webFluxValidator");
String[] jsrValidatorBeans = this.context
.getBeanNamesForType(javax.validation.Validator.class);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(webFluxValidator).isNotSameAs(customValidator);
assertThat(this.context.getBean(javax.validation.Validator.class))
.isEqualTo(customValidator);
assertThat(springValidatorBeans).containsExactly("jsr303ValidatorAdapter");
assertThat(jsrValidatorBeans).containsExactly("customValidator");
}
@Test
public void validationJsr303CustomValidatorReusedAsSpringValidator() {
load(CustomValidator.class);
assertThat(this.context.getBeansOfType(ValidatorFactory.class)).hasSize(1);
assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
.hasSize(1);
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(2);
Validator validator = this.context.getBean("webFluxValidator", Validator.class);
assertThat(validator).isInstanceOf(SpringValidator.class);
assertThat(((SpringValidator) validator).getTarget())
.isSameAs(this.context.getBean(javax.validation.Validator.class));
public void validatorWhenUserDefinedSingleJsr303AndSpringShouldUseDefined()
throws Exception {
load(UserDefinedSingleJsr303AndSpringValidator.class);
Object customValidator = this.context.getBean("customValidator");
Object webFluxValidator = this.context.getBean("webFluxValidator");
String[] jsrValidatorBeans = this.context
.getBeanNamesForType(javax.validation.Validator.class);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(webFluxValidator).isSameAs(customValidator);
assertThat(this.context.getBean(javax.validation.Validator.class))
.isEqualTo(customValidator);
assertThat(this.context.getBean(Validator.class)).isEqualTo(customValidator);
assertThat(springValidatorBeans).containsExactly("customValidator");
assertThat(jsrValidatorBeans).containsExactly("customValidator");
}
@Test
public void validationJsr303ValidatorExposedAsSpringValidator() {
load(Jsr303Validator.class);
assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
.hasSize(1);
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
Validator validator = this.context.getBean(Validator.class);
assertThat(validator).isInstanceOf(SpringValidator.class);
SpringValidatorAdapter target = ((SpringValidator) validator).getTarget();
assertThat(new DirectFieldAccessor(target).getPropertyValue("targetValidator"))
.isSameAs(this.context.getBean(javax.validation.Validator.class));
public void validatorWhenUserDefinedJsr303AndSpringShouldUseDefined()
throws Exception {
load(UserDefinedJsr303AndSpringValidator.class);
Object customJsrValidator = this.context.getBean("customJsrValidator");
Object customSpringValidator = this.context.getBean("customSpringValidator");
Object webFluxValidator = this.context.getBean("webFluxValidator");
String[] jsrValidatorBeans = this.context
.getBeanNamesForType(javax.validation.Validator.class);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(customJsrValidator).isNotSameAs(customSpringValidator);
assertThat(webFluxValidator).isSameAs(customSpringValidator);
assertThat(this.context.getBean(javax.validation.Validator.class))
.isEqualTo(customJsrValidator);
assertThat(this.context.getBean(Validator.class))
.isEqualTo(customSpringValidator);
assertThat(springValidatorBeans).containsExactly("customSpringValidator");
assertThat(jsrValidatorBeans).containsExactly("customJsrValidator");
}
@Test
public void validatorWhenExcludingValidatorAutoConfigurationShouldUseMvc()
throws Exception {
load(null, new Class[] { ValidationAutoConfiguration.class });
Object webFluxValidator = this.context.getBean("webFluxValidator");
String[] jsrValidatorBeans = this.context
.getBeanNamesForType(javax.validation.Validator.class);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(webFluxValidator).isInstanceOf(DelegatingValidator.class);
assertThat(springValidatorBeans).containsExactly("webFluxValidator");
assertThat(jsrValidatorBeans).isEmpty();
}
@Test
public void validatorWhenMultipleValidatorsAndNoWebFluxValidatorShouldAddMvc()
throws Exception {
load(MultipleValidatorsAndNoWebFluxValidator.class);
Object customValidator1 = this.context.getBean("customValidator1");
Object customValidator2 = this.context.getBean("customValidator2");
Object webFluxValidator = this.context.getBean("webFluxValidator");
String[] jsrValidatorBeans = this.context
.getBeanNamesForType(javax.validation.Validator.class);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(webFluxValidator).isNotSameAs(customValidator1)
.isNotSameAs(customValidator2);
assertThat(springValidatorBeans).containsExactly("customValidator1",
"customValidator2", "webFluxValidator");
assertThat(jsrValidatorBeans).isEmpty();
}
@Test
public void validatorWhenMultipleValidatorsAndWebFluxValidatorShouldUseMvc()
throws Exception {
load(MultipleValidatorsAndWebFluxValidator.class);
Object customValidator = this.context.getBean("customValidator");
Object webFluxValidator = this.context.getBean("webFluxValidator");
String[] jsrValidatorBeans = this.context
.getBeanNamesForType(javax.validation.Validator.class);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(webFluxValidator).isNotSameAs(customValidator);
assertThat(springValidatorBeans).containsExactly("customValidator",
"webFluxValidator");
assertThat(jsrValidatorBeans).isEmpty();
}
private void load(String... environment) {
@ -230,13 +307,24 @@ public class WebFluxAnnotationAutoConfigurationTests {
}
private void load(Class<?> config, String... environment) {
load(config, null, environment);
}
private void load(Class<?> config, Class<?>[] exclude, String... environment) {
this.context = new GenericReactiveWebApplicationContext();
EnvironmentTestUtils.addEnvironment(this.context, environment);
List<Class<?>> configClasses = new ArrayList<>();
if (config != null) {
this.context.register(config);
configClasses.add(config);
}
this.context.register(BaseConfiguration.class);
configClasses.addAll(Arrays.asList(Config.class,
ValidationAutoConfiguration.class, BaseConfiguration.class));
if (!ObjectUtils.isEmpty(exclude)) {
configClasses.removeAll(Arrays.asList(exclude));
}
this.context.register(configClasses.toArray(new Class<?>[configClasses.size()]));
this.context.refresh();
}
@Configuration
@ -291,47 +379,88 @@ public class WebFluxAnnotationAutoConfigurationTests {
}
@Configuration
protected static class WebFluxValidator implements WebFluxConfigurer {
private final Validator validator = mock(Validator.class);
protected static class ValidatorWebFluxConfigurer implements WebFluxConfigurer {
@Override
public Optional<Validator> getValidator() {
return Optional.of(this.validator);
return Optional.of(mock(Validator.class));
}
}
@Configuration
protected static class WebFluxJsr303Validator implements WebFluxConfigurer {
private final LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
@Override
public Optional<Validator> getValidator() {
return Optional.of(this.validator);
}
}
@Configuration
static class Jsr303Validator {
static class UserDefinedSpringOnlyValidator {
@Bean
public javax.validation.Validator jsr303Validator() {
public Validator customValidator() {
return mock(Validator.class);
}
}
@Configuration
static class UserDefinedJsr303Validator {
@Bean
public javax.validation.Validator customValidator() {
return mock(javax.validation.Validator.class);
}
}
@Configuration
static class CustomValidator {
static class UserDefinedSingleJsr303AndSpringValidator {
@Bean
public Validator customValidator() {
public LocalValidatorFactoryBean customValidator() {
return new LocalValidatorFactoryBean();
}
}
@Configuration
static class UserDefinedJsr303AndSpringValidator {
@Bean
public javax.validation.Validator customJsrValidator() {
return mock(javax.validation.Validator.class);
}
@Bean
public Validator customSpringValidator() {
return mock(Validator.class);
}
}
@Configuration
static class MultipleValidatorsAndNoWebFluxValidator {
@Bean
public Validator customValidator1() {
return mock(Validator.class);
}
@Bean
public Validator customValidator2() {
return mock(Validator.class);
}
}
@Configuration
static class MultipleValidatorsAndWebFluxValidator {
@Bean
public Validator customValidator() {
return mock(Validator.class);
}
@Bean
public Validator webFluxValidator() {
return mock(Validator.class);
}
}
}

View File

@ -27,7 +27,6 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ValidatorFactory;
import org.assertj.core.api.Condition;
import org.joda.time.DateTime;
@ -37,11 +36,13 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.validation.SpringValidator;
import org.springframework.boot.autoconfigure.validation.DelegatingValidator;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WelcomePageHandlerMapping;
import org.springframework.boot.test.util.EnvironmentTestUtils;
@ -62,11 +63,11 @@ import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.filter.HttpPutFormContentFilter;
@ -658,76 +659,154 @@ public class WebMvcAutoConfigurationTests {
}
@Test
public void validationNoJsr303ValidatorExposedByDefault() {
public void validatorWhenSuppliedByConfigurerShouldThrowException() throws Exception {
this.thrown.expect(BeanCreationException.class);
this.thrown.expectMessage("unexpected validator configuration");
load(ValidatorWebMvcConfigurer.class);
}
@Test
public void validatorWhenAutoConfiguredShouldUseAlias() throws Exception {
load();
assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
.isEmpty();
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
Object defaultValidator = this.context.getBean("defaultValidator");
Object mvcValidator = this.context.getBean("mvcValidator");
String[] jsrValidatorBeans = this.context
.getBeanNamesForType(javax.validation.Validator.class);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(mvcValidator).isSameAs(defaultValidator);
assertThat(springValidatorBeans).containsExactly("defaultValidator");
assertThat(jsrValidatorBeans).containsExactly("defaultValidator");
}
@Test
public void validationCustomConfigurerTakesPrecedence() {
load(MvcValidator.class);
assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
.isEmpty();
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
Validator validator = this.context.getBean(Validator.class);
assertThat(validator)
.isSameAs(this.context.getBean(MvcValidator.class).validator);
public void validatorWhenUserDefinedSpringOnlyShouldUseDefined() throws Exception {
load(UserDefinedSpringOnlyValidator.class);
Object customValidator = this.context.getBean("customValidator");
Object mvcValidator = this.context.getBean("mvcValidator");
String[] jsrValidatorBeans = this.context
.getBeanNamesForType(javax.validation.Validator.class);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(mvcValidator).isSameAs(customValidator);
assertThat(this.context.getBean(Validator.class)).isEqualTo(customValidator);
assertThat(springValidatorBeans).containsExactly("customValidator");
assertThat(jsrValidatorBeans).isEmpty();
}
@Test
public void validationCustomConfigurerTakesPrecedenceAndDoNotExposeJsr303() {
load(MvcJsr303Validator.class);
assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
.isEmpty();
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
Validator validator = this.context.getBean(Validator.class);
assertThat(validator).isInstanceOf(SpringValidator.class);
assertThat(((SpringValidator) validator).getTarget())
.isSameAs(this.context.getBean(MvcJsr303Validator.class).validator);
public void validatorWhenUserDefinedJsr303ShouldAdapt() throws Exception {
load(UserDefinedJsr303Validator.class);
Object customValidator = this.context.getBean("customValidator");
Object mvcValidator = this.context.getBean("mvcValidator");
String[] jsrValidatorBeans = this.context
.getBeanNamesForType(javax.validation.Validator.class);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(mvcValidator).isNotSameAs(customValidator);
assertThat(this.context.getBean(javax.validation.Validator.class))
.isEqualTo(customValidator);
assertThat(springValidatorBeans).containsExactly("jsr303ValidatorAdapter");
assertThat(jsrValidatorBeans).containsExactly("customValidator");
}
@Test
public void validationJsr303CustomValidatorReusedAsSpringValidator() {
load(CustomValidator.class);
assertThat(this.context.getBeansOfType(ValidatorFactory.class)).hasSize(1);
assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
.hasSize(1);
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(2);
Validator validator = this.context.getBean("mvcValidator", Validator.class);
assertThat(validator).isInstanceOf(SpringValidator.class);
assertThat(((SpringValidator) validator).getTarget())
.isSameAs(this.context.getBean(javax.validation.Validator.class));
public void validatorWhenUserDefinedSingleJsr303AndSpringShouldUseDefined()
throws Exception {
load(UserDefinedSingleJsr303AndSpringValidator.class);
Object customValidator = this.context.getBean("customValidator");
Object mvcValidator = this.context.getBean("mvcValidator");
String[] jsrValidatorBeans = this.context
.getBeanNamesForType(javax.validation.Validator.class);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(mvcValidator).isSameAs(customValidator);
assertThat(this.context.getBean(javax.validation.Validator.class))
.isEqualTo(customValidator);
assertThat(this.context.getBean(Validator.class)).isEqualTo(customValidator);
assertThat(springValidatorBeans).containsExactly("customValidator");
assertThat(jsrValidatorBeans).containsExactly("customValidator");
}
@Test
public void validationJsr303ValidatorExposedAsSpringValidator() {
load(Jsr303Validator.class);
assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty();
assertThat(this.context.getBeansOfType(javax.validation.Validator.class))
.hasSize(1);
assertThat(this.context.getBeansOfType(Validator.class)).hasSize(1);
Validator validator = this.context.getBean(Validator.class);
assertThat(validator).isInstanceOf(SpringValidator.class);
SpringValidatorAdapter target = ((SpringValidator) validator).getTarget();
assertThat(new DirectFieldAccessor(target).getPropertyValue("targetValidator"))
.isSameAs(this.context.getBean(javax.validation.Validator.class));
public void validatorWhenUserDefinedJsr303AndSpringShouldUseDefined()
throws Exception {
load(UserDefinedJsr303AndSpringValidator.class);
Object customJsrValidator = this.context.getBean("customJsrValidator");
Object customSpringValidator = this.context.getBean("customSpringValidator");
Object mvcValidator = this.context.getBean("mvcValidator");
String[] jsrValidatorBeans = this.context
.getBeanNamesForType(javax.validation.Validator.class);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(customJsrValidator).isNotSameAs(customSpringValidator);
assertThat(mvcValidator).isSameAs(customSpringValidator);
assertThat(this.context.getBean(javax.validation.Validator.class))
.isEqualTo(customJsrValidator);
assertThat(this.context.getBean(Validator.class))
.isEqualTo(customSpringValidator);
assertThat(springValidatorBeans).containsExactly("customSpringValidator");
assertThat(jsrValidatorBeans).containsExactly("customJsrValidator");
}
@Test
public void validatorWhenExcludingValidatorAutoConfigurationShouldUseMvc()
throws Exception {
load(null, new Class[] { ValidationAutoConfiguration.class });
Object mvcValidator = this.context.getBean("mvcValidator");
String[] jsrValidatorBeans = this.context
.getBeanNamesForType(javax.validation.Validator.class);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(mvcValidator).isInstanceOf(DelegatingValidator.class);
assertThat(springValidatorBeans).containsExactly("mvcValidator");
assertThat(jsrValidatorBeans).isEmpty();
}
@Test
public void validatorWhenMultipleValidatorsAndNoMvcValidatorShouldAddMvc()
throws Exception {
load(MultipleValidatorsAndNoMvcValidator.class);
Object customValidator1 = this.context.getBean("customValidator1");
Object customValidator2 = this.context.getBean("customValidator2");
Object mvcValidator = this.context.getBean("mvcValidator");
String[] jsrValidatorBeans = this.context
.getBeanNamesForType(javax.validation.Validator.class);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(mvcValidator).isNotSameAs(customValidator1)
.isNotSameAs(customValidator2);
assertThat(springValidatorBeans).containsExactly("customValidator1",
"customValidator2", "mvcValidator");
assertThat(jsrValidatorBeans).isEmpty();
}
@Test
public void validatorWhenMultipleValidatorsAndMvcValidatorShouldUseMvc()
throws Exception {
load(MultipleValidatorsAndMvcValidator.class);
Object customValidator = this.context.getBean("customValidator");
Object mvcValidator = this.context.getBean("mvcValidator");
String[] jsrValidatorBeans = this.context
.getBeanNamesForType(javax.validation.Validator.class);
String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class);
assertThat(mvcValidator).isNotSameAs(customValidator);
assertThat(springValidatorBeans).containsExactly("customValidator",
"mvcValidator");
assertThat(jsrValidatorBeans).isEmpty();
}
private void load(Class<?> config, String... environment) {
load(config, null, environment);
}
private void load(Class<?> config, Class<?>[] exclude, String... environment) {
this.context = new AnnotationConfigServletWebServerApplicationContext();
EnvironmentTestUtils.addEnvironment(this.context, environment);
List<Class<?>> configClasses = new ArrayList<>();
if (config != null) {
configClasses.add(config);
}
configClasses.addAll(Arrays.asList(Config.class, WebMvcAutoConfiguration.class,
configClasses.addAll(Arrays.asList(Config.class,
ValidationAutoConfiguration.class, WebMvcAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class));
if (!ObjectUtils.isEmpty(exclude)) {
configClasses.removeAll(Arrays.asList(exclude));
}
this.context.register(configClasses.toArray(new Class<?>[configClasses.size()]));
this.context.refresh();
}
@ -897,47 +976,88 @@ public class WebMvcAutoConfigurationTests {
}
@Configuration
protected static class MvcValidator extends WebMvcConfigurerAdapter {
private final Validator validator = mock(Validator.class);
protected static class ValidatorWebMvcConfigurer extends WebMvcConfigurerAdapter {
@Override
public Validator getValidator() {
return this.validator;
return mock(Validator.class);
}
}
@Configuration
protected static class MvcJsr303Validator extends WebMvcConfigurerAdapter {
private final LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
@Override
public Validator getValidator() {
return this.validator;
}
}
@Configuration
static class Jsr303Validator {
static class UserDefinedSpringOnlyValidator {
@Bean
public javax.validation.Validator jsr303Validator() {
public Validator customValidator() {
return mock(Validator.class);
}
}
@Configuration
static class UserDefinedJsr303Validator {
@Bean
public javax.validation.Validator customValidator() {
return mock(javax.validation.Validator.class);
}
}
@Configuration
static class CustomValidator {
static class UserDefinedSingleJsr303AndSpringValidator {
@Bean
public Validator customValidator() {
public LocalValidatorFactoryBean customValidator() {
return new LocalValidatorFactoryBean();
}
}
@Configuration
static class UserDefinedJsr303AndSpringValidator {
@Bean
public javax.validation.Validator customJsrValidator() {
return mock(javax.validation.Validator.class);
}
@Bean
public Validator customSpringValidator() {
return mock(Validator.class);
}
}
@Configuration
static class MultipleValidatorsAndNoMvcValidator {
@Bean
public Validator customValidator1() {
return mock(Validator.class);
}
@Bean
public Validator customValidator2() {
return mock(Validator.class);
}
}
@Configuration
static class MultipleValidatorsAndMvcValidator {
@Bean
public Validator customValidator() {
return mock(Validator.class);
}
@Bean
public Validator mvcValidator() {
return mock(Validator.class);
}
}
}

View File

@ -36,6 +36,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
@ -129,9 +130,9 @@ public class BasicErrorControllerDirectMockMvcTests {
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ ServletWebServerFactoryAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class,
WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
protected @interface MinimalWebConfiguration {
}

View File

@ -36,6 +36,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
@ -131,9 +132,9 @@ public class BasicErrorControllerMockMvcTests {
@Documented
@Import({ ServletWebServerFactoryAutoConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class,
WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
private @interface MinimalWebConfiguration {
}

View File

@ -92,4 +92,9 @@ public class OptionSetGroovyCompilerConfiguration implements GroovyCompilerConfi
return this.repositoryConfiguration;
}
@Override
public boolean isQuiet() {
return false;
}
}

View File

@ -140,7 +140,7 @@ public class RunCommand extends OptionParsingCommand {
@Override
public Level getLogLevel() {
if (getOptions().has(RunOptionHandler.this.quietOption)) {
if (isQuiet()) {
return Level.OFF;
}
if (getOptions().has(RunOptionHandler.this.verboseOption)) {
@ -149,6 +149,11 @@ public class RunCommand extends OptionParsingCommand {
return Level.INFO;
}
@Override
public boolean isQuiet() {
return getOptions().has(RunOptionHandler.this.quietOption);
}
}
}

View File

@ -95,7 +95,8 @@ public class GroovyCompiler {
new SpringBootDependenciesDependencyManagement());
AetherGrapeEngine grapeEngine = AetherGrapeEngineFactory.create(this.loader,
configuration.getRepositoryConfiguration(), resolutionContext);
configuration.getRepositoryConfiguration(), resolutionContext,
configuration.isQuiet());
GrapeEngineInstaller.install(grapeEngine);

View File

@ -71,4 +71,10 @@ public interface GroovyCompilerConfiguration {
*/
List<RepositoryConfiguration> getRepositoryConfiguration();
/**
* Returns if running in quiet mode.
* @return {@code true} if running in quiet mode
*/
boolean isQuiet();
}

View File

@ -77,7 +77,7 @@ public class AetherGrapeEngine implements GrapeEngine {
RepositorySystem repositorySystem,
DefaultRepositorySystemSession repositorySystemSession,
List<RemoteRepository> remoteRepositories,
DependencyResolutionContext resolutionContext) {
DependencyResolutionContext resolutionContext, boolean quiet) {
this.classLoader = classLoader;
this.repositorySystem = repositorySystem;
this.session = repositorySystemSession;
@ -88,12 +88,14 @@ public class AetherGrapeEngine implements GrapeEngine {
for (RemoteRepository repository : remotes) {
addRepository(repository);
}
this.progressReporter = getProgressReporter(this.session);
this.progressReporter = getProgressReporter(this.session, quiet);
}
private ProgressReporter getProgressReporter(DefaultRepositorySystemSession session) {
String progressReporter = System.getProperty(
"org.springframework.boot.cli.compiler.grape.ProgressReporter");
private ProgressReporter getProgressReporter(DefaultRepositorySystemSession session,
boolean quiet) {
String progressReporter = (quiet ? "none"
: System.getProperty(
"org.springframework.boot.cli.compiler.grape.ProgressReporter"));
if ("detail".equals(progressReporter)
|| Boolean.getBoolean("groovy.grape.report.downloads")) {
return new DetailedProgressReporter(session, System.out);

View File

@ -44,27 +44,21 @@ public abstract class AetherGrapeEngineFactory {
public static AetherGrapeEngine create(GroovyClassLoader classLoader,
List<RepositoryConfiguration> repositoryConfigurations,
DependencyResolutionContext dependencyResolutionContext) {
DependencyResolutionContext dependencyResolutionContext, boolean quiet) {
RepositorySystem repositorySystem = createServiceLocator()
.getService(RepositorySystem.class);
DefaultRepositorySystemSession repositorySystemSession = MavenRepositorySystemUtils
.newSession();
ServiceLoader<RepositorySystemSessionAutoConfiguration> autoConfigurations = ServiceLoader
.load(RepositorySystemSessionAutoConfiguration.class);
for (RepositorySystemSessionAutoConfiguration autoConfiguration : autoConfigurations) {
autoConfiguration.apply(repositorySystemSession, repositorySystem);
}
new DefaultRepositorySystemSessionAutoConfiguration()
.apply(repositorySystemSession, repositorySystem);
return new AetherGrapeEngine(classLoader, repositorySystem,
repositorySystemSession, createRepositories(repositoryConfigurations),
dependencyResolutionContext);
dependencyResolutionContext, quiet);
}
private static ServiceLocator createServiceLocator() {

View File

@ -81,6 +81,11 @@ public class GroovyGrabDependencyResolverTests {
return new String[] { "." };
}
@Override
public boolean isQuiet() {
return false;
}
};
this.resolver = new GroovyGrabDependencyResolver(configuration);
}

View File

@ -59,7 +59,7 @@ public class AetherGrapeEngineTests {
dependencyResolutionContext.addDependencyManagement(
new SpringBootDependenciesDependencyManagement());
return AetherGrapeEngineFactory.create(this.groovyClassLoader,
repositoryConfigurations, dependencyResolutionContext);
repositoryConfigurations, dependencyResolutionContext, false);
}
@Test

View File

@ -283,6 +283,8 @@ the `Main-Class` attribute and leave out `Start-Class`.
* `loader.path` can contain directories (scanned recursively for jar and zip files),
archive paths, a directory within an archive that is scanned for jar files (for
example, `dependencies.jar!/lib`), or wildcard patterns (for the default JVM behavior).
Archive paths can be relative to `loader.home`, or anywhere in the file system with a
`jar:file:` prefix.
* `loader.path` (if empty) defaults to `BOOT-INF/lib` (meaning a local directory or a
nested one if running from an archive). Because of this `PropertiesLauncher` behaves the
same as `JarLauncher` when no additional configuration is provided.

View File

@ -250,8 +250,7 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr
protected Class<?>[] getOrFindConfigurationClasses(
MergedContextConfiguration mergedConfig) {
Class<?>[] classes = mergedConfig.getClasses();
if (containsNonTestComponent(classes) || mergedConfig.hasLocations()
|| !mergedConfig.getContextInitializerClasses().isEmpty()) {
if (containsNonTestComponent(classes) || mergedConfig.hasLocations()) {
return classes;
}
Class<?> found = new SpringBootConfigurationFinder()

View File

@ -0,0 +1,66 @@
/*
* Copyright 2012-2016 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.test.context.bootstrap;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTestContextBootstrapper;
import org.springframework.boot.test.context.bootstrap.SpringBootTestContextBootstrapperWithInitializersTests.CustomInitializer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.BootstrapWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link SpringBootTestContextBootstrapper} with and
* {@link ApplicationContextInitializer}.
*
* @author Phillip Webb
*/
@RunWith(SpringRunner.class)
@BootstrapWith(SpringBootTestContextBootstrapper.class)
@ContextConfiguration(initializers = CustomInitializer.class)
public class SpringBootTestContextBootstrapperWithInitializersTests {
@Autowired
private ApplicationContext context;
@Test
public void foundConfiguration() throws Exception {
Object bean = this.context
.getBean(SpringBootTestContextBootstrapperExampleConfig.class);
assertThat(bean).isNotNull();
}
// gh-8483
public static class CustomInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
}
}
}

View File

@ -25,8 +25,10 @@ import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -299,11 +301,9 @@ public class PropertiesLauncher extends Launcher {
List<String> paths = new ArrayList<>();
for (String path : commaSeparatedPaths.split(",")) {
path = cleanupPath(path);
// Empty path (i.e. the archive itself if running from a JAR) is always added
// to the classpath so no need for it to be explicitly listed
if (!path.equals("")) {
paths.add(path);
}
// "" means the user wants root of archive but not current directory
path = ("".equals(path) ? "/" : path);
paths.add(path);
}
if (paths.isEmpty()) {
paths.add("lib");
@ -336,7 +336,13 @@ public class PropertiesLauncher extends Launcher {
@Override
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
ClassLoader loader = super.createClassLoader(archives);
Set<URL> urls = new LinkedHashSet<URL>(archives.size());
for (Archive archive : archives) {
urls.add(archive.getUrl());
}
ClassLoader loader = new LaunchedURLClassLoader(urls.toArray(new URL[0]),
getClass().getClassLoader());
debug("Classpath: " + urls);
String customLoaderClassName = getProperty("loader.classLoader");
if (customLoaderClassName != null) {
loader = wrapWithCustomClassLoader(loader, customLoaderClassName);
@ -454,13 +460,15 @@ public class PropertiesLauncher extends Launcher {
String root = cleanupPath(stripFileUrlPrefix(path));
List<Archive> lib = new ArrayList<>();
File file = new File(root);
if (!isAbsolutePath(root)) {
file = new File(this.home, root);
}
if (file.isDirectory()) {
debug("Adding classpath entries from " + file);
Archive archive = new ExplodedArchive(file, false);
lib.add(archive);
if (!"/".equals(root)) {
if (!isAbsolutePath(root)) {
file = new File(this.home, root);
}
if (file.isDirectory()) {
debug("Adding classpath entries from " + file);
Archive archive = new ExplodedArchive(file, false);
lib.add(archive);
}
}
Archive archive = getArchive(file);
if (archive != null) {
@ -488,24 +496,46 @@ public class PropertiesLauncher extends Launcher {
return null;
}
private List<Archive> getNestedArchives(String root) throws Exception {
if (root.startsWith("/")
|| this.parent.getUrl().equals(this.home.toURI().toURL())) {
private List<Archive> getNestedArchives(String path) throws Exception {
Archive parent = this.parent;
String root = path;
if (!root.equals("/") && root.startsWith("/")
|| parent.getUrl().equals(this.home.toURI().toURL())) {
// If home dir is same as parent archive, no need to add it twice.
return null;
}
Archive parent = this.parent;
if (root.startsWith("jar:file:") && root.contains("!")) {
if (root.contains("!")) {
int index = root.indexOf("!");
String file = root.substring("jar:file:".length(), index);
parent = new JarFileArchive(new File(file));
File file = new File(this.home, root.substring(0, index));
if (root.startsWith("jar:file:")) {
file = new File(root.substring("jar:file:".length(), index));
}
parent = new JarFileArchive(file);
root = root.substring(index + 1, root.length());
while (root.startsWith("/")) {
root = root.substring(1);
}
}
if (root.endsWith(".jar")) {
File file = new File(this.home, root);
if (file.exists()) {
parent = new JarFileArchive(file);
root = "";
}
}
if (root.equals("/") || root.equals("./") || root.equals(".")) {
// The prefix for nested jars is actually empty if it's at the root
root = "";
}
EntryFilter filter = new PrefixMatchingArchiveFilter(root);
return parent.getNestedArchives(filter);
List<Archive> archives = new ArrayList<Archive>(parent.getNestedArchives(filter));
if (("".equals(root) || ".".equals(root)) && !path.endsWith(".jar")
&& parent != this.parent) {
// You can't find the root with an entry filter so it has to be added
// explicitly. But don't add the root of the parent archive.
archives.add(parent);
}
return archives;
}
private void addNestedEntries(List<Archive> lib) {
@ -518,7 +548,7 @@ public class PropertiesLauncher extends Launcher {
@Override
public boolean matches(Entry entry) {
if (entry.isDirectory()) {
return entry.getName().startsWith(JarLauncher.BOOT_INF_CLASSES);
return entry.getName().equals(JarLauncher.BOOT_INF_CLASSES);
}
return entry.getName().startsWith(JarLauncher.BOOT_INF_LIB);
}
@ -607,6 +637,9 @@ public class PropertiesLauncher extends Launcher {
@Override
public boolean matches(Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals(this.prefix);
}
return entry.getName().startsWith(this.prefix) && this.filter.matches(entry);
}

View File

@ -21,12 +21,13 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import org.assertj.core.api.Condition;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@ -36,6 +37,9 @@ import org.junit.rules.TemporaryFolder;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.ExplodedArchive;
import org.springframework.boot.loader.archive.JarFileArchive;
import org.springframework.core.io.FileSystemResource;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
@ -72,6 +76,7 @@ public class PropertiesLauncherTests {
System.clearProperty("loader.config.name");
System.clearProperty("loader.config.location");
System.clearProperty("loader.system");
System.clearProperty("loader.classLoader");
}
@Test
@ -131,6 +136,16 @@ public class PropertiesLauncherTests {
.isEqualTo("[.]");
}
@Test
public void testUserSpecifiedSlashPath() throws Exception {
System.setProperty("loader.path", "jars/");
PropertiesLauncher launcher = new PropertiesLauncher();
assertThat(ReflectionTestUtils.getField(launcher, "paths").toString())
.isEqualTo("[jars/]");
List<Archive> archives = launcher.getClassPathArchives();
assertThat(archives).areExactly(1, endingWith("app.jar!/"));
}
@Test
public void testUserSpecifiedWildcardPath() throws Exception {
System.setProperty("loader.path", "jars/*");
@ -153,13 +168,44 @@ public class PropertiesLauncherTests {
waitFor("Hello World");
}
@Test
public void testUserSpecifiedRootOfJarPath() throws Exception {
System.setProperty("loader.path",
"jar:file:./src/test/resources/nested-jars/app.jar!/");
PropertiesLauncher launcher = new PropertiesLauncher();
assertThat(ReflectionTestUtils.getField(launcher, "paths").toString())
.isEqualTo("[jar:file:./src/test/resources/nested-jars/app.jar!/]");
List<Archive> archives = launcher.getClassPathArchives();
assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
assertThat(archives).areExactly(1, endingWith("app.jar!/"));
}
@Test
public void testUserSpecifiedRootOfJarPathWithDot() throws Exception {
System.setProperty("loader.path", "nested-jars/app.jar!/./");
PropertiesLauncher launcher = new PropertiesLauncher();
List<Archive> archives = launcher.getClassPathArchives();
assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
assertThat(archives).areExactly(1, endingWith("app.jar!/"));
}
@Test
public void testUserSpecifiedRootOfJarPathWithDotAndJarPrefix() throws Exception {
System.setProperty("loader.path",
"jar:file:./src/test/resources/nested-jars/app.jar!/./");
PropertiesLauncher launcher = new PropertiesLauncher();
List<Archive> archives = launcher.getClassPathArchives();
assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
}
@Test
public void testUserSpecifiedJarFileWithNestedArchives() throws Exception {
System.setProperty("loader.path", "nested-jars/app.jar");
System.setProperty("loader.main", "demo.Application");
PropertiesLauncher launcher = new PropertiesLauncher();
launcher.launch(new String[0]);
waitFor("Hello World");
List<Archive> archives = launcher.getClassPathArchives();
assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
assertThat(archives).areExactly(1, endingWith("app.jar!/"));
}
@Test
@ -209,11 +255,28 @@ public class PropertiesLauncherTests {
public void testCustomClassLoaderCreation() throws Exception {
System.setProperty("loader.classLoader", TestLoader.class.getName());
PropertiesLauncher launcher = new PropertiesLauncher();
ClassLoader loader = launcher.createClassLoader(Collections.<Archive>emptyList());
ClassLoader loader = launcher.createClassLoader(archives());
assertThat(loader).isNotNull();
assertThat(loader.getClass().getName()).isEqualTo(TestLoader.class.getName());
}
private List<Archive> archives() throws Exception {
List<Archive> archives = new ArrayList<Archive>();
String path = System.getProperty("java.class.path");
for (String url : path.split(File.pathSeparator)) {
archives.add(archive(url));
}
return archives;
}
private Archive archive(String url) throws IOException {
File file = new FileSystemResource(url).getFile();
if (url.endsWith(".jar")) {
return new JarFileArchive(file);
}
return new ExplodedArchive(file);
}
@Test
public void testUserSpecifiedConfigPathWins() throws Exception {
@ -280,6 +343,17 @@ public class PropertiesLauncherTests {
assertThat(timeout).as("Timed out waiting for (" + value + ")").isTrue();
}
private Condition<Archive> endingWith(final String value) {
return new Condition<Archive>() {
@Override
public boolean matches(Archive archive) {
return archive.toString().endsWith(value);
}
};
}
public static class TestLoader extends URLClassLoader {
public TestLoader(ClassLoader parent) {