diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/BeanMetaDataStore.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/BeanMetaDataStore.java
new file mode 100644
index 0000000000..c7e844097f
--- /dev/null
+++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/BeanMetaDataStore.java
@@ -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 @Bean definition meta data during initialization
+ * of the bean factory.
+ *
+ * @author Dave Syer
+ */
+class BeanMetaDataStore implements BeanFactoryPostProcessor {
+
+ private ConfigurableListableBeanFactory beanFactory;
+
+ private Map beans = new HashMap();
+
+ @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 found = new AtomicReference();
+ 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;
+ }
+
+ }
+
+}
diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationProperties.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationProperties.java
index 7a14cdf651..2f62a630b5 100644
--- a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationProperties.java
+++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationProperties.java
@@ -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 {
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 6ba45f10fb..ada35b1dcb 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
@@ -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);
diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorRegistrar.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorRegistrar.java
index b84b1f5100..3ebd7616e3 100644
--- a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorRegistrar.java
+++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorRegistrar.java
@@ -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());
}
}
-
}
diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesTests.java
index f8a8d10dc6..8002c0b79a 100644
--- a/spring-boot/src/test/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesTests.java
+++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesTests.java
@@ -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.