diff --git a/spring-autoconfigure/src/main/java/org/springframework/autoconfigure/web/EmbeddedServletContainerAutoConfiguration.java b/spring-autoconfigure/src/main/java/org/springframework/autoconfigure/web/EmbeddedServletContainerAutoConfiguration.java index 1e117b3ab8..7c0498260e 100644 --- a/spring-autoconfigure/src/main/java/org/springframework/autoconfigure/web/EmbeddedServletContainerAutoConfiguration.java +++ b/spring-autoconfigure/src/main/java/org/springframework/autoconfigure/web/EmbeddedServletContainerAutoConfiguration.java @@ -22,10 +22,17 @@ import org.apache.catalina.startup.Tomcat; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.Loader; import org.springframework.autoconfigure.EnableAutoConfiguration; +import org.springframework.autoconfigure.web.EmbeddedServletContainerAutoConfiguration.EmbeddedServletContainerCustomizerBeanPostProcessorRegistrar; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.bootstrap.context.condition.ConditionalOnClass; import org.springframework.bootstrap.context.condition.ConditionalOnMissingBean; import org.springframework.bootstrap.context.condition.SearchStrategy; -import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerCustomizer; import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor; import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerFactory; import org.springframework.bootstrap.context.embedded.ServletContextInitializer; @@ -33,8 +40,11 @@ import org.springframework.bootstrap.context.embedded.jetty.JettyEmbeddedServlet import org.springframework.bootstrap.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import org.springframework.core.type.AnnotationMetadata; import org.springframework.web.servlet.DispatcherServlet; /** @@ -44,18 +54,10 @@ import org.springframework.web.servlet.DispatcherServlet; * @author Dave Syer */ @Order(Ordered.HIGHEST_PRECEDENCE) +@Configuration +@Import(EmbeddedServletContainerCustomizerBeanPostProcessorRegistrar.class) public class EmbeddedServletContainerAutoConfiguration { - /** - * Support {@link EmbeddedServletContainerCustomizerBeanPostProcessor} to apply - * {@link EmbeddedServletContainerCustomizer}s. - */ - @Bean - @ConditionalOnMissingBean(value = EmbeddedServletContainerCustomizerBeanPostProcessor.class, search = SearchStrategy.CURRENT) - public EmbeddedServletContainerCustomizerBeanPostProcessor embeddedServletContainerCustomizerBeanPostProcessor() { - return new EmbeddedServletContainerCustomizerBeanPostProcessor(); - } - /** * Add the {@link DispatcherServlet} unless the user has defined their own * {@link ServletContextInitializer}s. @@ -101,4 +103,35 @@ public class EmbeddedServletContainerAutoConfiguration { } + /** + * Registers a {@link EmbeddedServletContainerCustomizerBeanPostProcessor}. Registered + * via {@link ImportBeanDefinitionRegistrar} for early registration. + */ + public static class EmbeddedServletContainerCustomizerBeanPostProcessorRegistrar + implements ImportBeanDefinitionRegistrar, BeanFactoryAware { + + private ConfigurableListableBeanFactory beanFactory; + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + if (beanFactory instanceof ConfigurableListableBeanFactory) { + this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; + } + } + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + BeanDefinitionRegistry registry) { + if (this.beanFactory != null + && this.beanFactory.getBeansOfType( + EmbeddedServletContainerCustomizerBeanPostProcessor.class) + .size() == 0) { + BeanDefinition beanDefinition = new RootBeanDefinition( + EmbeddedServletContainerCustomizerBeanPostProcessor.class); + registry.registerBeanDefinition( + "embeddedServletContainerCustomizerBeanPostProcessor", + beanDefinition); + } + } + } } diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/properties/ConfigurationPropertiesBindingConfiguration.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/properties/ConfigurationPropertiesBindingConfiguration.java deleted file mode 100644 index 1d5fb675c9..0000000000 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/properties/ConfigurationPropertiesBindingConfiguration.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2012-2013 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.bootstrap.context.properties; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.bootstrap.context.condition.ConditionalOnClass; -import org.springframework.bootstrap.context.condition.ConditionalOnMissingBean; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; -import org.springframework.core.convert.ConversionService; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.Environment; -import org.springframework.core.env.MutablePropertySources; -import org.springframework.core.env.PropertySource; -import org.springframework.core.env.PropertySources; -import org.springframework.validation.Validator; -import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; - -/** - * Configuration for binding externalized application properties to - * {@link ConfigurationProperties} beans. - * - * @author Dave Syer - */ -@Configuration -public class ConfigurationPropertiesBindingConfiguration { - - public final static String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator"; - - @Autowired(required = false) - private PropertySourcesPlaceholderConfigurer configurer; - - @Autowired(required = false) - private Environment environment; - - @Autowired(required = false) - @Qualifier(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME) - private ConversionService conversionService; - - @Autowired(required = false) - @Qualifier(VALIDATOR_BEAN_NAME) - private Validator validator; - - @ConditionalOnMissingBean(name = VALIDATOR_BEAN_NAME) - @ConditionalOnClass(name = "javax.validation.Validator") - protected static class ValidatorConfiguration { - @Bean - protected Validator configurationPropertiesValidator() { - return new LocalValidatorFactoryBean(); - } - } - - /** - * Lifecycle hook that binds application properties to any bean whose type is - * decorated with {@link ConfigurationProperties} annotation. - * - * @return a bean post processor to bind application properties - */ - @Bean - public ConfigurationPropertiesBindingPostProcessor propertySourcesBinder() { - PropertySources propertySources; - - if (this.configurer != null) { - propertySources = extractPropertySources(this.configurer); - } - else if (this.environment instanceof ConfigurableEnvironment) { - propertySources = flattenPropertySources(((ConfigurableEnvironment) this.environment) - .getPropertySources()); - } - else { - // empty, so not very useful, but fulfils the contract - propertySources = new MutablePropertySources(); - } - - ConfigurationPropertiesBindingPostProcessor processor = new ConfigurationPropertiesBindingPostProcessor(); - processor.setValidator(this.validator); - processor.setConversionService(this.conversionService); - processor.setPropertySources(propertySources); - return processor; - } - - /** - * Flatten out a tree of property sources. - * - * @param propertySources some PropertySources, possibly containing environment - * properties - * @return another PropertySources containing the same properties - */ - private PropertySources flattenPropertySources(PropertySources propertySources) { - MutablePropertySources result = new MutablePropertySources(); - for (PropertySource propertySource : propertySources) { - flattenPropertySources(propertySource, result); - } - return result; - } - - /** - * Convenience method to allow recursive flattening of property sources. - * - * @param propertySource a property source to flatten - * @param result the cumulative result - */ - private void flattenPropertySources(PropertySource propertySource, - MutablePropertySources result) { - Object source = propertySource.getSource(); - if (source instanceof ConfigurableEnvironment) { - ConfigurableEnvironment environment = (ConfigurableEnvironment) source; - for (PropertySource childSource : environment.getPropertySources()) { - flattenPropertySources(childSource, result); - } - } - else { - result.addLast(propertySource); - } - } - - /** - * Convenience method to extract PropertySources from an existing (and already - * initialized) PropertySourcesPlaceholderConfigurer. As long as this method is - * executed late enough in the context lifecycle it will come back with data. We can - * rely on the fact that PropertySourcesPlaceholderConfigurer is a - * BeanFactoryPostProcessor and is therefore initialized early. - * - * @param configurer a PropertySourcesPlaceholderConfigurer - * @return some PropertySources - */ - private PropertySources extractPropertySources( - PropertySourcesPlaceholderConfigurer configurer) { - PropertySources propertySources = configurer.getAppliedPropertySources(); - // Flatten the sources into a single list so they can be iterated - return flattenPropertySources(propertySources); - } - -} diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/properties/ConfigurationPropertiesBindingPostProcessor.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/properties/ConfigurationPropertiesBindingPostProcessor.java index 321c1b4cd5..fff1436532 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/properties/ConfigurationPropertiesBindingPostProcessor.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/properties/ConfigurationPropertiesBindingPostProcessor.java @@ -17,21 +17,30 @@ package org.springframework.bootstrap.context.properties; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +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.config.BeanPostProcessor; import org.springframework.bootstrap.bind.PropertiesConfigurationFactory; import org.springframework.bootstrap.config.PropertiesPropertySourceLoader; import org.springframework.bootstrap.config.PropertySourceLoader; import org.springframework.bootstrap.config.YamlPropertySourceLoader; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; @@ -40,22 +49,32 @@ import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; +import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import org.springframework.validation.Validator; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; /** * {@link BeanPostProcessor} to bind {@link PropertySources} to beans annotated with * {@link ConfigurationProperties}. * * @author Dave Syer + * @author Phillip Webb */ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor, - BeanFactoryAware, ResourceLoaderAware, EnvironmentAware { + BeanFactoryAware, ResourceLoaderAware, EnvironmentAware, BeanClassLoaderAware, + ApplicationContextAware, InitializingBean, DisposableBean { + + public static final String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator"; + + private static final String VALIDATOR_CLASS = "javax.validation.Validator"; private PropertySources propertySources; private Validator validator; + private boolean ownedValidator = false; + private ConversionService conversionService; private DefaultConversionService defaultConversionService = new DefaultConversionService(); @@ -68,6 +87,10 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc private Environment environment = new StandardEnvironment(); + private ClassLoader beanClassLoader; + + private ApplicationContext applicationContext; + /** * @param propertySources */ @@ -104,6 +127,126 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc this.environment = environment; } + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Override + public void afterPropertiesSet() throws Exception { + + if (this.propertySources == null) { + this.propertySources = deducePropertySources(); + } + + if (this.validator == null) { + this.validator = getOptionalBean(VALIDATOR_BEAN_NAME, Validator.class); + if (this.validator == null + && ClassUtils.isPresent(VALIDATOR_CLASS, this.beanClassLoader)) { + LocalValidatorFactoryBean validatorToUse = new LocalValidatorFactoryBean(); + validatorToUse.setApplicationContext(this.applicationContext); + validatorToUse.afterPropertiesSet(); + this.validator = validatorToUse; + this.ownedValidator = true; + } + } + + if (this.conversionService == null) { + this.conversionService = getOptionalBean( + ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME, + ConversionService.class); + } + } + + @Override + public void destroy() throws Exception { + if (this.ownedValidator) { + ((DisposableBean) this.validator).destroy(); + } + } + + private PropertySources deducePropertySources() { + try { + PropertySourcesPlaceholderConfigurer configurer = this.beanFactory + .getBean(PropertySourcesPlaceholderConfigurer.class); + return extractPropertySources(configurer); + } + catch (NoSuchBeanDefinitionException ex) { + // Continue if no PropertySourcesPlaceholderConfigurer bean + } + + if (this.environment instanceof ConfigurableEnvironment) { + return flattenPropertySources(((ConfigurableEnvironment) this.environment) + .getPropertySources()); + } + + // empty, so not very useful, but fulfils the contract + return new MutablePropertySources(); + } + + private T getOptionalBean(String name, Class type) { + try { + return this.beanFactory.getBean(name, type); + } + catch (NoSuchBeanDefinitionException ex) { + return null; + } + } + + /** + * Convenience method to extract PropertySources from an existing (and already + * initialized) PropertySourcesPlaceholderConfigurer. As long as this method is + * executed late enough in the context lifecycle it will come back with data. We can + * rely on the fact that PropertySourcesPlaceholderConfigurer is a + * BeanFactoryPostProcessor and is therefore initialized early. + * @param configurer a PropertySourcesPlaceholderConfigurer + * @return some PropertySources + */ + private PropertySources extractPropertySources( + PropertySourcesPlaceholderConfigurer configurer) { + PropertySources propertySources = configurer.getAppliedPropertySources(); + // Flatten the sources into a single list so they can be iterated + return flattenPropertySources(propertySources); + } + + /** + * Flatten out a tree of property sources. + * @param propertySources some PropertySources, possibly containing environment + * properties + * @return another PropertySources containing the same properties + */ + private PropertySources flattenPropertySources(PropertySources propertySources) { + MutablePropertySources result = new MutablePropertySources(); + for (PropertySource propertySource : propertySources) { + flattenPropertySources(propertySource, result); + } + return result; + } + + /** + * Convenience method to allow recursive flattening of property sources. + * @param propertySource a property source to flatten + * @param result the cumulative result + */ + private void flattenPropertySources(PropertySource propertySource, + MutablePropertySources result) { + Object source = propertySource.getSource(); + if (source instanceof ConfigurableEnvironment) { + ConfigurableEnvironment environment = (ConfigurableEnvironment) source; + for (PropertySource childSource : environment.getPropertySources()) { + flattenPropertySources(childSource, result); + } + } + else { + result.addLast(propertySource); + } + } + @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { @@ -130,7 +273,8 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc if (annotation != null && annotation.path().length != 0) { factory.setPropertySources(loadPropertySources(annotation.path())); - } else { + } + else { factory.setPropertySources(this.propertySources); } factory.setValidator(this.validator); @@ -149,15 +293,18 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc } try { factory.bindPropertiesToTarget(); - } catch (Exception ex) { + } + catch (Exception ex) { throw new BeanCreationException(beanName, "Could not bind properties", ex); } } private PropertySources loadPropertySources(String[] path) { MutablePropertySources propertySources = new MutablePropertySources(); - PropertySourceLoader[] loaders = { new PropertiesPropertySourceLoader(), - YamlPropertySourceLoader.springProfileAwareLoader(environment.getActiveProfiles()) }; + PropertySourceLoader[] loaders = { + new PropertiesPropertySourceLoader(), + YamlPropertySourceLoader.springProfileAwareLoader(this.environment + .getActiveProfiles()) }; for (String location : path) { location = this.environment.resolvePlaceholders(location); Resource resource = this.resourceLoader.getResource(location); diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/properties/ConfigurationPropertiesBindingPostProcessorRegistrar.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/properties/ConfigurationPropertiesBindingPostProcessorRegistrar.java new file mode 100644 index 0000000000..be4c5b8ee4 --- /dev/null +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/properties/ConfigurationPropertiesBindingPostProcessorRegistrar.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2013 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.bootstrap.context.properties; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + * {@link ImportBeanDefinitionRegistrar} for binding externalized application properties + * to {@link ConfigurationProperties} beans. + * + * @author Dave Syer + * @author Phillip Webb + */ +public class ConfigurationPropertiesBindingPostProcessorRegistrar implements + ImportBeanDefinitionRegistrar { + + public static final String BINDER_BEAN_NAME = "propertySourcesBinder"; + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + BeanDefinitionRegistry registry) { + BeanDefinition beanDefinition = new RootBeanDefinition( + ConfigurationPropertiesBindingPostProcessor.class); + registry.registerBeanDefinition(BINDER_BEAN_NAME, beanDefinition); + } + +} diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/properties/EnableConfigurationPropertiesImportSelector.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/properties/EnableConfigurationPropertiesImportSelector.java index 95c170621f..a8d3516236 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/properties/EnableConfigurationPropertiesImportSelector.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/properties/EnableConfigurationPropertiesImportSelector.java @@ -48,11 +48,11 @@ class EnableConfigurationPropertiesImportSelector implements ImportSelector { EnableConfigurationProperties.class.getName(), false); Object[] type = (Object[]) attributes.getFirst("value"); if (type == null || type.length == 0) { - return new String[] { ConfigurationPropertiesBindingConfiguration.class + return new String[] { ConfigurationPropertiesBindingPostProcessorRegistrar.class .getName() }; } return new String[] { ConfigurationPropertiesBeanRegistrar.class.getName(), - ConfigurationPropertiesBindingConfiguration.class.getName() }; + ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() }; } public static class ConfigurationPropertiesBeanRegistrar implements