Enable @ConfigurationProperties on a @Bean definition
This allows multiple beans of the same type to be bound with different prefixes. As a side effect you get default binding if the bean class is itself @ConfigurationProperties.
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.boot.context.properties;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.MethodCallback;
|
||||
|
||||
/**
|
||||
* Utility class to memoize <code>@Bean</code> definition meta data during initialization
|
||||
* of the bean factory.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
class BeanMetaDataStore implements BeanFactoryPostProcessor {
|
||||
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
private Map<String, MetaData> beans = new HashMap<String, MetaData>();
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
|
||||
throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
for (String name : beanFactory.getBeanDefinitionNames()) {
|
||||
BeanDefinition definition = beanFactory.getBeanDefinition(name);
|
||||
String method = definition.getFactoryMethodName();
|
||||
String bean = definition.getFactoryBeanName();
|
||||
if (method != null && bean != null) {
|
||||
this.beans.put(name, new MetaData(bean, method));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Method findFactoryMethod(String beanName) {
|
||||
if (!this.beans.containsKey(beanName)) {
|
||||
return null;
|
||||
}
|
||||
final AtomicReference<Method> found = new AtomicReference<Method>();
|
||||
MetaData meta = this.beans.get(beanName);
|
||||
final String factory = meta.getMethod();
|
||||
Class<?> type = this.beanFactory.getType(meta.getBean());
|
||||
ReflectionUtils.doWithMethods(type, new MethodCallback() {
|
||||
@Override
|
||||
public void doWith(Method method) throws IllegalArgumentException,
|
||||
IllegalAccessException {
|
||||
if (method.getName().equals(factory)) {
|
||||
found.set(method);
|
||||
}
|
||||
}
|
||||
});
|
||||
return found.get();
|
||||
}
|
||||
|
||||
private static class MetaData {
|
||||
|
||||
private String bean;
|
||||
|
||||
private String method;
|
||||
|
||||
public MetaData(String bean, String method) {
|
||||
this.bean = bean;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public String getBean() {
|
||||
return this.bean;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,7 +29,7 @@ import java.lang.annotation.Target;
|
||||
* @author Dave Syer
|
||||
* @see ConfigurationPropertiesBindingPostProcessor
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface ConfigurationProperties {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.boot.context.properties;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
@@ -74,6 +75,8 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
|
||||
private static final String[] VALIDATOR_CLASSES = { "javax.validation.Validator",
|
||||
"javax.validation.ValidatorFactory" };
|
||||
|
||||
private BeanMetaDataStore beans = new BeanMetaDataStore();
|
||||
|
||||
private PropertySources propertySources;
|
||||
|
||||
private Validator validator;
|
||||
@@ -132,6 +135,13 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param beans the bean meta data to set
|
||||
*/
|
||||
public void setBeanMetaDataStore(BeanMetaDataStore beans) {
|
||||
this.beans = beans;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
@@ -277,9 +287,24 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
|
||||
if (annotation != null || bean instanceof ConfigurationPropertiesHolder) {
|
||||
postProcessBeforeInitialization(bean, beanName, annotation);
|
||||
}
|
||||
annotation = maybePostProcessAnnotatedFactoryMethod(bean, beanName);
|
||||
if (annotation != null) {
|
||||
postProcessBeforeInitialization(bean, beanName, annotation);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
private ConfigurationProperties maybePostProcessAnnotatedFactoryMethod(Object bean,
|
||||
String beanName) {
|
||||
Method method = this.beans.findFactoryMethod(beanName);
|
||||
if (method != null) {
|
||||
ConfigurationProperties annotation = AnnotationUtils.findAnnotation(method,
|
||||
ConfigurationProperties.class);
|
||||
return annotation;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName)
|
||||
throws BeansException {
|
||||
@@ -353,7 +378,7 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
|
||||
}
|
||||
|
||||
private ConversionService getDefaultConversionService() {
|
||||
if (!this.initialized && this.beanFactory instanceof ListableBeanFactory) {
|
||||
if (!this.initialized) {
|
||||
for (Converter<?, ?> converter : ((ListableBeanFactory) this.beanFactory)
|
||||
.getBeansOfType(Converter.class).values()) {
|
||||
this.defaultConversionService.addConverter(converter);
|
||||
|
||||
@@ -16,9 +16,8 @@
|
||||
|
||||
package org.springframework.boot.context.properties;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
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;
|
||||
|
||||
@@ -35,14 +34,19 @@ public class ConfigurationPropertiesBindingPostProcessorRegistrar implements
|
||||
public static final String BINDER_BEAN_NAME = ConfigurationPropertiesBindingPostProcessor.class
|
||||
.getName();
|
||||
|
||||
private static final String METADATA_BEAN_NAME = BINDER_BEAN_NAME + ".store";
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
|
||||
BeanDefinitionRegistry registry) {
|
||||
if (!registry.containsBeanDefinition(BINDER_BEAN_NAME)) {
|
||||
BeanDefinition beanDefinition = new RootBeanDefinition(
|
||||
ConfigurationPropertiesBindingPostProcessor.class);
|
||||
registry.registerBeanDefinition(BINDER_BEAN_NAME, beanDefinition);
|
||||
BeanDefinitionBuilder meta = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(BeanMetaDataStore.class);
|
||||
BeanDefinitionBuilder bean = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(ConfigurationPropertiesBindingPostProcessor.class);
|
||||
bean.addPropertyReference("beanMetaDataStore", METADATA_BEAN_NAME);
|
||||
registry.registerBeanDefinition(BINDER_BEAN_NAME, bean.getBeanDefinition());
|
||||
registry.registerBeanDefinition(METADATA_BEAN_NAME, meta.getBeanDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -370,6 +370,27 @@ public class EnableConfigurationPropertiesTests {
|
||||
assertEquals("value12", bean.mymap.get("key1.key2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAnnotatedBean() {
|
||||
EnvironmentTestUtils.addEnvironment(this.context, "external.name:bar",
|
||||
"spam.name:foo");
|
||||
this.context.register(TestConfigurationWithAnnotatedBean.class);
|
||||
this.context.refresh();
|
||||
assertEquals("foo", this.context.getBean(External.class).getName());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
public static class TestConfigurationWithAnnotatedBean {
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties(prefix = "spam")
|
||||
public External testProperties() {
|
||||
return new External();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Strict tests need a known set of properties so we remove system items which may be
|
||||
* environment specific.
|
||||
|
||||
Reference in New Issue
Block a user