Add @Bean-level config properties to /configprops

This commit is contained in:
Dave Syer
2014-05-12 16:47:30 +01:00
parent 0d27ce4850
commit 85719f75e4
6 changed files with 224 additions and 25 deletions

View File

@@ -47,6 +47,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.bind.PropertiesConfigurationFactory;
import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
@@ -71,13 +72,16 @@ public class EndpointAutoConfiguration {
private InfoPropertiesConfiguration properties;
@Autowired(required = false)
private final MetricReader metricRepository = new InMemoryMetricRepository();
private MetricReader metricRepository = new InMemoryMetricRepository();
@Autowired(required = false)
private PublicMetrics metrics;
@Autowired(required = false)
private final TraceRepository traceRepository = new InMemoryTraceRepository();
private TraceRepository traceRepository = new InMemoryTraceRepository();
@Autowired(required = false)
private ConfigurationBeanFactoryMetaData beanFactoryMetaData;
@Bean
@ConditionalOnMissingBean
@@ -159,7 +163,9 @@ public class EndpointAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint() {
return new ConfigurationPropertiesReportEndpoint();
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint();
endpoint.setConfigurationBeanFactoryMetaData(this.beanFactoryMetaData);
return endpoint;
}
@Configuration

View File

@@ -20,6 +20,7 @@ import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@@ -47,6 +48,7 @@ import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
* in your Spring Boot application configuration.
*
* @author Christian Dupuis
* @author Dave Syer
*/
@ConfigurationProperties(prefix = "endpoints.configprops", ignoreUnknownFields = false)
public class ConfigurationPropertiesReportEndpoint extends
@@ -58,6 +60,8 @@ public class ConfigurationPropertiesReportEndpoint extends
private ApplicationContext context;
private ConfigurationBeanFactoryMetaData beanFactoryMetaData;
public ConfigurationPropertiesReportEndpoint() {
super("configprops");
}
@@ -71,6 +75,11 @@ public class ConfigurationPropertiesReportEndpoint extends
this.context = context;
}
public void setConfigurationBeanFactoryMetaData(
ConfigurationBeanFactoryMetaData beanFactoryMetaData) {
this.beanFactoryMetaData = beanFactoryMetaData;
}
public void setKeysToSanitize(String... keysToSanitize) {
Assert.notNull(keysToSanitize, "KeysToSanitize must not be null");
this.keysToSanitize = keysToSanitize;
@@ -87,9 +96,14 @@ public class ConfigurationPropertiesReportEndpoint extends
*/
@SuppressWarnings("unchecked")
protected Map<String, Object> extract(ApplicationContext context) {
Map<String, Object> result = new HashMap<String, Object>();
Map<String, Object> beans = context
.getBeansWithAnnotation(ConfigurationProperties.class);
Map<String, Object> beans = new HashMap<String, Object>(
context.getBeansWithAnnotation(ConfigurationProperties.class));
if (this.beanFactoryMetaData != null) {
beans.putAll(this.beanFactoryMetaData
.getBeansWithFactoryAnnotation(ConfigurationProperties.class));
}
// Serialize beans into map structure and sanitize values
ObjectMapper mapper = new ObjectMapper();
@@ -100,7 +114,7 @@ public class ConfigurationPropertiesReportEndpoint extends
Object bean = entry.getValue();
Map<String, Object> root = new HashMap<String, Object>();
root.put("prefix", extractPrefix(bean));
root.put("prefix", extractPrefix(beanName, bean));
root.put("properties", sanitize(mapper.convertValue(bean, Map.class)));
result.put(beanName, root);
}
@@ -134,9 +148,19 @@ public class ConfigurationPropertiesReportEndpoint extends
/**
* Extract configuration prefix from {@link ConfigurationProperties} annotation.
*/
private String extractPrefix(Object bean) {
private String extractPrefix(String beanName, Object bean) {
ConfigurationProperties annotation = AnnotationUtils.findAnnotation(
bean.getClass(), ConfigurationProperties.class);
if (this.beanFactoryMetaData != null) {
ConfigurationProperties override = this.beanFactoryMetaData
.findFactoryAnnotation(beanName, ConfigurationProperties.class);
if (override != null) {
// The @Bean-level @ConfigurationProperties overrides the one at type
// level when binding. Arguably we should render them both, but this one
// might be the most relevant for a starting point.
annotation = override;
}
}
return (StringUtils.hasLength(annotation.value()) ? annotation.value()
: annotation.prefix());
}

View File

@@ -0,0 +1,159 @@
/*
* Copyright 2013-2014 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.actuate.endpoint;
import java.util.Map;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class ConfigurationPropertiesReportEndpointMethodAnnotationsTests {
private AnnotationConfigApplicationContext context;
@Before
public void setup() {
this.context = new AnnotationConfigApplicationContext();
}
@After
public void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
@SuppressWarnings("unchecked")
public void testNaming() throws Exception {
this.context.register(Config.class);
EnvironmentTestUtils.addEnvironment(this.context, "other.name:foo",
"first.name:bar");
this.context.refresh();
ConfigurationPropertiesReportEndpoint report = this.context
.getBean(ConfigurationPropertiesReportEndpoint.class);
Map<String, Object> properties = report.invoke();
Map<String, Object> nestedProperties = (Map<String, Object>) properties
.get("other");
assertNotNull(nestedProperties);
assertEquals("other", nestedProperties.get("prefix"));
assertNotNull(nestedProperties.get("properties"));
}
@Test
@SuppressWarnings("unchecked")
public void testOverride() throws Exception {
this.context.register(Other.class);
EnvironmentTestUtils.addEnvironment(this.context, "other.name:foo");
this.context.refresh();
ConfigurationPropertiesReportEndpoint report = this.context
.getBean(ConfigurationPropertiesReportEndpoint.class);
Map<String, Object> properties = report.invoke();
Map<String, Object> nestedProperties = (Map<String, Object>) properties
.get("bar");
assertNotNull(nestedProperties);
assertEquals("other", nestedProperties.get("prefix"));
assertNotNull(nestedProperties.get("properties"));
}
@Configuration
@EnableConfigurationProperties
public static class Config {
@Bean
public ConfigurationPropertiesReportEndpoint endpoint(
ConfigurationBeanFactoryMetaData beanFactoryMetaData) {
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint();
endpoint.setConfigurationBeanFactoryMetaData(beanFactoryMetaData);
return endpoint;
}
@Bean
@ConfigurationProperties(prefix = "first")
public Foo foo() {
return new Foo();
}
@Bean
@ConfigurationProperties(prefix = "other")
public Foo other() {
return new Foo();
}
}
@Configuration
@EnableConfigurationProperties
public static class Other {
@Bean
public ConfigurationPropertiesReportEndpoint endpoint(
ConfigurationBeanFactoryMetaData beanFactoryMetaData) {
ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint();
endpoint.setConfigurationBeanFactoryMetaData(beanFactoryMetaData);
return endpoint;
}
@Bean
@ConfigurationProperties(prefix = "other")
public Bar bar() {
return new Bar();
}
}
public static class Foo {
private String name = "654321";
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
@ConfigurationProperties(prefix = "test")
public static class Bar {
private String name = "654321";
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.boot.context.properties;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
@@ -25,6 +26,7 @@ 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.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodCallback;
@@ -34,7 +36,7 @@ import org.springframework.util.ReflectionUtils.MethodCallback;
*
* @author Dave Syer
*/
class BeanMetaDataStore implements BeanFactoryPostProcessor {
public class ConfigurationBeanFactoryMetaData implements BeanFactoryPostProcessor {
private ConfigurableListableBeanFactory beanFactory;
@@ -54,7 +56,26 @@ class BeanMetaDataStore implements BeanFactoryPostProcessor {
}
}
public Method findFactoryMethod(String beanName) {
public <A extends Annotation> Map<String, Object> getBeansWithFactoryAnnotation(
Class<A> type) {
Map<String, Object> result = new HashMap<String, Object>();
for (String name : this.beans.keySet()) {
if (findFactoryAnnotation(name, type) != null) {
result.put(name, this.beanFactory.getBean(name));
}
}
return result;
}
public <A extends Annotation> A findFactoryAnnotation(String beanName, Class<A> type) {
Method method = findFactoryMethod(beanName);
if (method != null) {
return AnnotationUtils.findAnnotation(method, type);
}
return null;
}
private Method findFactoryMethod(String beanName) {
if (!this.beans.containsKey(beanName)) {
return null;
}

View File

@@ -17,7 +17,6 @@
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;
@@ -75,7 +74,7 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
private static final String[] VALIDATOR_CLASSES = { "javax.validation.Validator",
"javax.validation.ValidatorFactory" };
private BeanMetaDataStore beans = new BeanMetaDataStore();
private ConfigurationBeanFactoryMetaData beans = new ConfigurationBeanFactoryMetaData();
private PropertySources propertySources;
@@ -138,7 +137,7 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
/**
* @param beans the bean meta data to set
*/
public void setBeanMetaDataStore(BeanMetaDataStore beans) {
public void setBeanMetaDataStore(ConfigurationBeanFactoryMetaData beans) {
this.beans = beans;
}
@@ -287,24 +286,14 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
if (annotation != null || bean instanceof ConfigurationPropertiesHolder) {
postProcessBeforeInitialization(bean, beanName, annotation);
}
annotation = maybePostProcessAnnotatedFactoryMethod(bean, beanName);
annotation = this.beans.findFactoryAnnotation(beanName,
ConfigurationProperties.class);
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 {

View File

@@ -41,7 +41,7 @@ public class ConfigurationPropertiesBindingPostProcessorRegistrar implements
BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(BINDER_BEAN_NAME)) {
BeanDefinitionBuilder meta = BeanDefinitionBuilder
.genericBeanDefinition(BeanMetaDataStore.class);
.genericBeanDefinition(ConfigurationBeanFactoryMetaData.class);
BeanDefinitionBuilder bean = BeanDefinitionBuilder
.genericBeanDefinition(ConfigurationPropertiesBindingPostProcessor.class);
bean.addPropertyReference("beanMetaDataStore", METADATA_BEAN_NAME);