diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java index 3e4707f4ba7..b3efdaa69a0 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java @@ -36,9 +36,11 @@ import org.springframework.boot.bind.PropertiesConfigurationFactory; import org.springframework.boot.env.PropertySourcesLoader; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; +import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; @@ -71,9 +73,10 @@ import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; * @author Christian Dupuis * @author Stephane Nicoll */ -public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor, - BeanFactoryAware, ResourceLoaderAware, EnvironmentAware, ApplicationContextAware, - InitializingBean, DisposableBean, PriorityOrdered { +public class ConfigurationPropertiesBindingPostProcessor + implements BeanPostProcessor, BeanFactoryAware, ResourceLoaderAware, + EnvironmentAware, ApplicationContextAware, InitializingBean, DisposableBean, + ApplicationListener, PriorityOrdered { /** * The bean name of the configuration properties validator. @@ -87,7 +90,7 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc private PropertySources propertySources; - private Validator validator; + private volatile Validator validator; private boolean ownedValidator = false; @@ -192,6 +195,22 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc if (this.propertySources == null) { this.propertySources = deducePropertySources(); } + initializeValidator(); + if (this.conversionService == null) { + this.conversionService = getOptionalBean( + ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME, + ConversionService.class); + } + } + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + if (this.validator != null && isJsr303Present()) { + this.validator = null; // allow it to be garbage collected + } + } + + private void initializeValidator() { if (this.validator == null) { this.validator = getOptionalBean(VALIDATOR_BEAN_NAME, Validator.class); if (this.validator == null && isJsr303Present()) { @@ -200,11 +219,6 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc this.ownedValidator = true; } } - if (this.conversionService == null) { - this.conversionService = getOptionalBean( - ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME, - ConversionService.class); - } } private boolean isJsr303Present() { @@ -219,7 +233,7 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc @Override public void destroy() throws Exception { - if (this.ownedValidator) { + if (this.ownedValidator && this.validator != null) { ((DisposableBean) this.validator).destroy(); } } @@ -339,6 +353,7 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc } private Validator determineValidator(Object bean) { + initializeValidator(); boolean globalValidatorSupportBean = (this.validator != null && this.validator.supports(bean.getClass())); if (ClassUtils.isAssignable(Validator.class, bean.getClass())) { diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java index a67e9e13d1f..49d1cfa7dfc 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java @@ -40,6 +40,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.mock.env.MockEnvironment; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.validation.BindException; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; @@ -51,6 +52,7 @@ import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -116,6 +118,16 @@ public class ConfigurationPropertiesBindingPostProcessorTests { assertBindingFailure(2); } + @Test + public void testValidationAndNullOutValidator() { + this.context = new AnnotationConfigApplicationContext(); + this.context.register(TestConfiguration.class); + this.context.refresh(); + ConfigurationPropertiesBindingPostProcessor bean = this.context + .getBean(ConfigurationPropertiesBindingPostProcessor.class); + assertNull(ReflectionTestUtils.getField(bean, "validator")); + } + @Test public void testSuccessfulValidationWithJSR303() { MockEnvironment env = new MockEnvironment();