Commit b63016d8 authored by Greg Turnquist's avatar Greg Turnquist Committed by Dave Syer

Create a report based on Boot's autoconfiguration decisions

- Gather autoconfiguration conditional decisiions (true and false)
- Provide an actuator endpoint as one means to read the report
- Define @EnableAutConfigurationReport annotation to turn this feature on
- Tidy up autoconfig report a bit and log it if --debug=true
parent 089233e4
...@@ -22,6 +22,7 @@ import java.util.Properties; ...@@ -22,6 +22,7 @@ import java.util.Properties;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint;
import org.springframework.boot.actuate.endpoint.BeansEndpoint; import org.springframework.boot.actuate.endpoint.BeansEndpoint;
import org.springframework.boot.actuate.endpoint.DumpEndpoint; import org.springframework.boot.actuate.endpoint.DumpEndpoint;
import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.actuate.endpoint.Endpoint;
...@@ -41,7 +42,9 @@ import org.springframework.boot.actuate.trace.InMemoryTraceRepository; ...@@ -41,7 +42,9 @@ import org.springframework.boot.actuate.trace.InMemoryTraceRepository;
import org.springframework.boot.actuate.trace.TraceRepository; import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.report.AutoConfigurationReport;
import org.springframework.boot.bind.PropertiesConfigurationFactory; import org.springframework.boot.bind.PropertiesConfigurationFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
...@@ -54,9 +57,10 @@ import org.springframework.http.MediaType; ...@@ -54,9 +57,10 @@ import org.springframework.http.MediaType;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for common management * {@link EnableAutoConfiguration Auto-configuration} for common management
* {@link Endpoint}s. * {@link Endpoint}s.
* *
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb * @author Phillip Webb
* @author Greg Turnquist
*/ */
@Configuration @Configuration
@ConditionalOnClass(MediaType.class) @ConditionalOnClass(MediaType.class)
...@@ -128,6 +132,13 @@ public class EndpointAutoConfiguration { ...@@ -128,6 +132,13 @@ public class EndpointAutoConfiguration {
return new DumpEndpoint(); return new DumpEndpoint();
} }
@Bean
@ConditionalOnBean(AutoConfigurationReport.class)
@ConditionalOnMissingBean
public AutoConfigurationReportEndpoint autoConfigurationAuditEndpoint() {
return new AutoConfigurationReportEndpoint();
}
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public ShutdownEndpoint shutdownEndpoint() { public ShutdownEndpoint shutdownEndpoint() {
...@@ -139,7 +150,6 @@ public class EndpointAutoConfiguration { ...@@ -139,7 +150,6 @@ public class EndpointAutoConfiguration {
@Autowired @Autowired
private ConfigurableEnvironment environment = new StandardEnvironment(); private ConfigurableEnvironment environment = new StandardEnvironment();
@Value("${spring.git.properties:classpath:git.properties}") @Value("${spring.git.properties:classpath:git.properties}")
private Resource gitProperties; private Resource gitProperties;
......
...@@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; ...@@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.Outcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration.DefaultTemplateResolverConfiguration; import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration.DefaultTemplateResolverConfiguration;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
......
/*
* 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.actuate.endpoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.report.AutoConfigurationReport;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Endpoint to serve up autoconfiguration report if actuator is on the classpath.
*
* @author Greg Turnquist
*/
@ConfigurationProperties(name = "endpoints.autoconfigurationreport", ignoreUnknownFields = false)
public class AutoConfigurationReportEndpoint extends AbstractEndpoint<AutoConfigurationReport> {
@Autowired
private AutoConfigurationReport autoConfigurationReport;
public AutoConfigurationReportEndpoint() {
super("/autoconfigurationreport");
}
@Override
public AutoConfigurationReport invoke() {
return this.autoConfigurationReport;
}
}
...@@ -18,8 +18,9 @@ package org.springframework.boot.actuate.autoconfigure; ...@@ -18,8 +18,9 @@ package org.springframework.boot.actuate.autoconfigure;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.TestUtils; import org.springframework.boot.TestUtils;
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration; import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint;
import org.springframework.boot.actuate.endpoint.BeansEndpoint; import org.springframework.boot.actuate.endpoint.BeansEndpoint;
import org.springframework.boot.actuate.endpoint.DumpEndpoint; import org.springframework.boot.actuate.endpoint.DumpEndpoint;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint;
...@@ -28,6 +29,7 @@ import org.springframework.boot.actuate.endpoint.InfoEndpoint; ...@@ -28,6 +29,7 @@ import org.springframework.boot.actuate.endpoint.InfoEndpoint;
import org.springframework.boot.actuate.endpoint.MetricsEndpoint; import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
import org.springframework.boot.actuate.endpoint.TraceEndpoint; import org.springframework.boot.actuate.endpoint.TraceEndpoint;
import org.springframework.boot.autoconfigure.report.AutoConfigurationReportCreator;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
...@@ -39,6 +41,7 @@ import static org.junit.Assert.assertNull; ...@@ -39,6 +41,7 @@ import static org.junit.Assert.assertNull;
* *
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb * @author Phillip Webb
* @author Greg Turnquist
*/ */
public class EndpointAutoConfigurationTests { public class EndpointAutoConfigurationTests {
...@@ -63,6 +66,20 @@ public class EndpointAutoConfigurationTests { ...@@ -63,6 +66,20 @@ public class EndpointAutoConfigurationTests {
assertNotNull(this.context.getBean(TraceEndpoint.class)); assertNotNull(this.context.getBean(TraceEndpoint.class));
} }
@Test(expected = NoSuchBeanDefinitionException.class)
public void noAutoConfigurationAuditEndpointByDefault() {
this.context.getBean(AutoConfigurationReportEndpoint.class);
}
@Test
public void autoconfigurationAuditEndpoints() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(EndpointAutoConfiguration.class, AutoConfigurationReportCreator.class);
this.context.refresh();
assertNotNull(this.context.getBean(AutoConfigurationReportEndpoint.class));
}
@Test @Test
public void testInfoEndpointConfiguration() throws Exception { public void testInfoEndpointConfiguration() throws Exception {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
......
/*
* 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.actuate.endpoint;
import org.springframework.boot.autoconfigure.report.EnableAutoConfigurationReport;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Tests for {@link AutoConfigurationReportEndpoint}.
*
* @author Greg Turnquist
*/
public class AutoConfigurationReportEndpointTests extends AbstractEndpointTests<AutoConfigurationReportEndpoint> {
public AutoConfigurationReportEndpointTests() {
super(Config.class, AutoConfigurationReportEndpoint.class,
"/autoconfigurationreport", true, "endpoints.autoconfigurationreport");
}
@Configuration
@EnableConfigurationProperties
@EnableAutoConfigurationReport
public static class Config {
@Bean
public AutoConfigurationReportEndpoint endpoint() {
return new AutoConfigurationReportEndpoint();
}
}
}
/*
* 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.autoconfigure.condition;
/**
* Outcome for a match, including log message.
*/
public class Outcome {
private final boolean match;
private final String message;
public Outcome(boolean match, String message) {
this.match = match;
this.message = message;
}
public static Outcome match() {
return match(null);
}
public static Outcome match(String message) {
return new Outcome(true, message);
}
public static Outcome noMatch(String message) {
return new Outcome(false, message);
}
public boolean isMatch() {
return this.match;
}
public String getMessage() {
return this.message;
}
}
...@@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.condition; ...@@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.condition;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.boot.autoconfigure.report.AutoConfigurationReport;
import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.AnnotatedTypeMetadata;
...@@ -31,6 +32,7 @@ import org.springframework.util.StringUtils; ...@@ -31,6 +32,7 @@ import org.springframework.util.StringUtils;
* logging to help the user diagnose what classes are loaded. * logging to help the user diagnose what classes are loaded.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Greg Turnquist
*/ */
public abstract class SpringBootCondition implements Condition { public abstract class SpringBootCondition implements Condition {
...@@ -39,18 +41,22 @@ public abstract class SpringBootCondition implements Condition { ...@@ -39,18 +41,22 @@ public abstract class SpringBootCondition implements Condition {
@Override @Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Outcome result = getMatchOutcome(context, metadata); Outcome result = getMatchOutcome(context, metadata);
StringBuilder message = getMessage(metadata, result);
if (!result.isMatch()) { if (!result.isMatch()) {
// Log non-matching conditions at debug // Log non-matching conditions at debug
if (this.logger.isDebugEnabled()) { if (this.logger.isDebugEnabled()) {
this.logger.debug(getMessage(metadata, result)); this.logger.debug(message);
} }
AutoConfigurationReport.registerDecision(context, message.toString(), getClassOrMethodName(metadata), result);
return false; return false;
} }
// Log matching conditions at trace // Log matching conditions at trace
if (this.logger.isTraceEnabled()) { if (this.logger.isTraceEnabled()) {
this.logger.trace(getMessage(metadata, result)); this.logger.trace(message);
} }
AutoConfigurationReport.registerDecision(context, message.toString(), getClassOrMethodName(metadata), result);
return true; return true;
} }
...@@ -59,22 +65,27 @@ public abstract class SpringBootCondition implements Condition { ...@@ -59,22 +65,27 @@ public abstract class SpringBootCondition implements Condition {
message.append("Condition "); message.append("Condition ");
message.append(ClassUtils.getShortName(getClass())); message.append(ClassUtils.getShortName(getClass()));
message.append(" on "); message.append(" on ");
message.append(getClassOrMethodName(metadata));
message.append(result.isMatch() ? " matched" : " did not match");
if (StringUtils.hasLength(result.getMessage())) {
message.append(" due to ");
message.append(result.getMessage());
}
return message;
}
private String getClassOrMethodName(AnnotatedTypeMetadata metadata) {
if (metadata instanceof ClassMetadata) { if (metadata instanceof ClassMetadata) {
ClassMetadata classMetadata = (ClassMetadata) metadata; ClassMetadata classMetadata = (ClassMetadata) metadata;
message.append(classMetadata.getClassName()); return classMetadata.getClassName();
} }
else if (metadata instanceof MethodMetadata) { else if (metadata instanceof MethodMetadata) {
MethodMetadata methodMetadata = (MethodMetadata) metadata; MethodMetadata methodMetadata = (MethodMetadata) metadata;
message.append(methodMetadata.getDeclaringClassName()); return methodMetadata.getDeclaringClassName() + "#" + methodMetadata.getMethodName();
message.append("#");
message.append(methodMetadata.getMethodName());
} }
message.append(result.isMatch() ? " matched" : " did not match"); else {
if (StringUtils.hasLength(result.getMessage())) { return "";
message.append(" due to ");
message.append(result.getMessage());
} }
return message;
} }
/** /**
...@@ -102,39 +113,4 @@ public abstract class SpringBootCondition implements Condition { ...@@ -102,39 +113,4 @@ public abstract class SpringBootCondition implements Condition {
return condition.matches(context, metadata); return condition.matches(context, metadata);
} }
/**
* Outcome for a match, including log message.
*/
protected final static class Outcome {
private final boolean match;
private final String message;
public Outcome(boolean match, String message) {
this.match = match;
this.message = message;
}
public boolean isMatch() {
return this.match;
}
public String getMessage() {
return this.message;
}
public static Outcome match() {
return match(null);
}
public static Outcome match(String message) {
return new Outcome(true, message);
}
public static Outcome noMatch(String message) {
return new Outcome(false, message);
}
}
} }
...@@ -33,6 +33,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; ...@@ -33,6 +33,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.Outcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
......
/*
* 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.autoconfigure.report;
import org.springframework.boot.autoconfigure.condition.Outcome;
/**
* Collects details about decision made during autoconfiguration (pass or fail)
*
* @author Greg Turnquist
*/
public class AutoConfigurationDecision {
private final String message;
private final String classOrMethodName;
private final Outcome outcome;
public AutoConfigurationDecision(String message, String classOrMethodName, Outcome outcome) {
this.message = message;
this.classOrMethodName = classOrMethodName;
this.outcome = outcome;
}
public String getMessage() {
return message;
}
public String getClassOrMethodName() {
return classOrMethodName;
}
public Outcome getOutcome() {
return outcome;
}
@Override
public String toString() {
return "AutoConfigurationDecision{" + "message='" + message + '\''
+ ", classOrMethodName='" + classOrMethodName + '\'' + ", outcome="
+ outcome + '}';
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
AutoConfigurationDecision decision = (AutoConfigurationDecision) o;
if (message != null ? !message.equals(decision.message)
: decision.message != null)
return false;
if (outcome != null ? !outcome.equals(decision.outcome)
: decision.outcome != null)
return false;
return true;
}
@Override
public int hashCode() {
int result = message != null ? message.hashCode() : 0;
result = 31 * result + (outcome != null ? outcome.hashCode() : 0);
return result;
}
}
/*
* 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.autoconfigure.report;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.boot.autoconfigure.condition.Outcome;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.event.ContextRefreshedEvent;
/**
* Bean used to gather autoconfiguration decisions, and then generate a collection of info
* for beans that were created as well as situations where the conditional outcome was
* negative.
*
* @author Greg Turnquist
* @author Dave Syer
*/
public class AutoConfigurationReport implements ApplicationContextAware,
ApplicationListener<ContextRefreshedEvent> {
private static Log logger = LogFactory.getLog(AutoConfigurationReport.class);
private Set<CreatedBeanInfo> beansCreated = new LinkedHashSet<CreatedBeanInfo>();
private Map<String, List<AutoConfigurationDecision>> autoconfigurationDecisions = new LinkedHashMap<String, List<AutoConfigurationDecision>>();
private Map<String, List<String>> positive = new LinkedHashMap<String, List<String>>();
private Map<String, List<String>> negative = new LinkedHashMap<String, List<String>>();
private ApplicationContext context;
private boolean initialized = false;
public static void registerDecision(ConditionContext context, String message,
String classOrMethodName, Outcome outcome) {
if (context.getBeanFactory().containsBeanDefinition("autoConfigurationReport")) {
AutoConfigurationReport autoconfigurationReport = context.getBeanFactory()
.getBean(AutoConfigurationReport.class);
autoconfigurationReport.registerDecision(message, classOrMethodName, outcome);
}
}
private void registerDecision(String message, String classOrMethodName,
Outcome outcome) {
AutoConfigurationDecision decision = new AutoConfigurationDecision(message,
classOrMethodName, outcome);
if (!this.autoconfigurationDecisions.containsKey(classOrMethodName)) {
this.autoconfigurationDecisions.put(classOrMethodName,
new ArrayList<AutoConfigurationDecision>());
}
this.autoconfigurationDecisions.get(classOrMethodName).add(decision);
}
public Set<CreatedBeanInfo> getBeansCreated() {
return this.beansCreated;
}
public Map<String, List<String>> getNegativeDecisions() {
return this.negative;
}
public Set<Class<?>> getBeanTypesCreated() {
Set<Class<?>> beanTypesCreated = new HashSet<Class<?>>();
for (CreatedBeanInfo bootCreatedBeanInfo : this.getBeansCreated()) {
beanTypesCreated.add(bootCreatedBeanInfo.getBeanType());
}
return beanTypesCreated;
}
public Set<String> getBeanNamesCreated() {
Set<String> beanNamesCreated = new HashSet<String>();
for (CreatedBeanInfo bootCreatedBeanInfo : this.getBeansCreated()) {
beanNamesCreated.add(bootCreatedBeanInfo.getName());
}
return beanNamesCreated;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.context = applicationContext;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
initialize();
}
public void initialize() {
if (!this.initialized) {
synchronized (this) {
if (!this.initialized) {
this.initialized = true;
splitDecisionsIntoPositiveAndNegative();
scanPositiveDecisionsForBeansBootCreated();
if (this.context.getEnvironment().getProperty("debug", Boolean.class,
false)) {
logger.info("Created beans:");
for (CreatedBeanInfo info : this.beansCreated) {
logger.info(info);
}
logger.info("Negative decisions:");
for (String key : this.negative.keySet()) {
logger.info(key + ": " + this.negative.get(key));
}
}
}
}
}
}
/**
* Scan the list of {@link AutoConfigurationDecision}'s, and if all outcomes true,
* then put it on the positive list. Otherwise, put it on the negative list.
*/
private synchronized void splitDecisionsIntoPositiveAndNegative() {
for (String key : this.autoconfigurationDecisions.keySet()) {
boolean match = true;
for (AutoConfigurationDecision decision : this.autoconfigurationDecisions
.get(key)) {
if (!decision.getOutcome().isMatch()) {
match = false;
}
}
if (match) {
if (!this.positive.containsKey(key)) {
this.positive.put(key, new ArrayList<String>());
}
for (AutoConfigurationDecision decision : this.autoconfigurationDecisions
.get(key)) {
this.positive.get(key).add(decision.getMessage());
}
}
else {
if (!this.negative.containsKey(key)) {
this.negative.put(key, new ArrayList<String>());
}
for (AutoConfigurationDecision decision : this.autoconfigurationDecisions
.get(key)) {
this.negative.get(key).add(decision.getMessage());
}
}
}
}
/**
* Scan all the decisions based on successful outcome, and try to find the
* corresponding beans Boot created.
*/
private synchronized void scanPositiveDecisionsForBeansBootCreated() {
for (String key : this.positive.keySet()) {
for (AutoConfigurationDecision decision : this.autoconfigurationDecisions
.get(key)) {
for (String beanName : this.context.getBeanDefinitionNames()) {
Object bean = this.context.getBean(beanName);
if (decision.getMessage().contains(beanName)
&& decision.getMessage().contains("matched")) {
boolean anyMethodsAreBeans = false;
for (Method method : bean.getClass().getMethods()) {
if (this.context.containsBean(method.getName())) {
this.beansCreated.add(new CreatedBeanInfo(method
.getName(), method.getReturnType(), this.positive
.get(key)));
anyMethodsAreBeans = true;
}
}
if (!anyMethodsAreBeans) {
this.beansCreated.add(new CreatedBeanInfo(beanName, bean,
this.positive.get(key)));
}
}
}
}
}
}
}
/*
* 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.autoconfigure.report;
import java.util.List;
/**
* A collection of data about a bean created by Boot
*
* @author Greg Turnquist
*/
public class BootCreatedBeanInfo {
private final String beanName;
private final Class<?> beanType;
private final List<String> decisions;
public BootCreatedBeanInfo(String beanName, Object bean, List<String> decisions) {
this.beanName = beanName;
this.beanType = bean.getClass();
this.decisions = decisions;
}
public BootCreatedBeanInfo(String beanName, Class<?> declaredBeanType, List<String> decisions) {
this.beanName = beanName;
this.beanType = declaredBeanType;
this.decisions = decisions;
}
@Override
public String toString() {
return "BootCreatedBeanInfo{" + "beanName='" + beanName + '\'' + ", beanType=" + beanType
+ ", decisions=" + decisions + '}';
}
public String getBeanName() {
return beanName;
}
public Class<?> getBeanType() {
return beanType;
}
public List<String> getDecisions() {
return decisions;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
BootCreatedBeanInfo bootCreatedBeanInfo = (BootCreatedBeanInfo) o;
if (beanName != null ? !beanName.equals(bootCreatedBeanInfo.beanName)
: bootCreatedBeanInfo.beanName != null)
return false;
return true;
}
@Override
public int hashCode() {
return beanName != null ? beanName.hashCode() : 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.boot.autoconfigure.report;
import java.util.List;
/**
* A collection of data about a bean created by Boot
*
* @author Greg Turnquist
*/
public class CreatedBeanInfo {
private final String name;
private final Class<?> type;
private final List<String> decisions;
public CreatedBeanInfo(String beanName, Object bean, List<String> decisions) {
this.name = beanName;
this.type = bean.getClass();
this.decisions = decisions;
}
public CreatedBeanInfo(String beanName, Class<?> declaredBeanType,
List<String> decisions) {
this.name = beanName;
this.type = declaredBeanType;
this.decisions = decisions;
}
@Override
public String toString() {
return "{" + "name='" + this.name + '\'' + ", type=" + this.type + ", decisions="
+ this.decisions + '}';
}
public String getName() {
return this.name;
}
public Class<?> getBeanType() {
return this.type;
}
public List<String> getDecisions() {
return this.decisions;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
CreatedBeanInfo bootCreatedBeanInfo = (CreatedBeanInfo) o;
if (this.name != null ? !this.name.equals(bootCreatedBeanInfo.name)
: bootCreatedBeanInfo.name != null)
return false;
return true;
}
@Override
public int hashCode() {
return this.name != null ? this.name.hashCode() : 0;
}
}
...@@ -24,6 +24,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; ...@@ -24,6 +24,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.Outcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
......
/*
* 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.autoconfigure;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Test;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
import org.springframework.boot.autoconfigure.jms.JmsTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.report.AutoConfigurationReport;
import org.springframework.boot.autoconfigure.report.CreatedBeanInfo;
import org.springframework.boot.autoconfigure.report.EnableAutoConfigurationReport;
import org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.jms.core.JmsTemplate;
/**
* Tests for {@link AutoConfigurationReport}.
*
* @author Greg Turnquist
*/
public class AutoConfigurationReportTests {
private AnnotationConfigApplicationContext context;
@Test
public void simpleReportTestCase() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(TestConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
AutoConfigurationReport autoconfigSettings = this.context
.getBean(AutoConfigurationReport.class);
Set<CreatedBeanInfo> beansBootCreated = autoconfigSettings.getBeansCreated();
Set<String> beanNamesBootCreated = autoconfigSettings.getBeanNamesCreated();
Set<Class<?>> beanTypesBootCreated = autoconfigSettings.getBeanTypesCreated();
assertEquals(1, beansBootCreated.size());
assertEquals(1, beanNamesBootCreated.size());
assertEquals(1, beanTypesBootCreated.size());
assertTrue(beanNamesBootCreated.contains("propertySourcesPlaceholderConfigurer"));
assertTrue(beanTypesBootCreated
.contains(PropertySourcesPlaceholderConfigurer.class));
boolean foundPropertySourcesPlaceHolderConfigurer = false;
int totalDecisions = 0;
for (CreatedBeanInfo item : beansBootCreated) {
for (String decision : item.getDecisions()) {
totalDecisions += 1;
if (decision.contains("propertySourcesPlaceholderConfigurer matched")) {
foundPropertySourcesPlaceHolderConfigurer = true;
}
}
}
assertEquals(1, totalDecisions);
assertTrue(foundPropertySourcesPlaceHolderConfigurer);
}
@Test
public void rabbitReportTest() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(TestConfiguration.class, RabbitAutoConfiguration.class);
this.context.refresh();
AutoConfigurationReport autoconfigSettings = this.context
.getBean(AutoConfigurationReport.class);
Set<CreatedBeanInfo> beansBootCreated = autoconfigSettings.getBeansCreated();
Set<String> beanNamesBootCreated = autoconfigSettings.getBeanNamesCreated();
Set<Class<?>> beanTypesBootCreated = autoconfigSettings.getBeanTypesCreated();
assertEquals(3, beansBootCreated.size());
assertEquals(3, beanNamesBootCreated.size());
assertEquals(3, beanTypesBootCreated.size());
assertTrue(beanNamesBootCreated.contains("amqpAdmin"));
assertTrue(beanNamesBootCreated.contains("rabbitConnectionFactory"));
assertTrue(beanNamesBootCreated.contains("rabbitTemplate"));
assertTrue(beanTypesBootCreated.contains(RabbitAdmin.class));
assertTrue(beanTypesBootCreated.contains(ConnectionFactory.class));
assertTrue(beanTypesBootCreated.contains(RabbitTemplate.class));
boolean foundRabbitConnectionFactory = false;
boolean foundAmqpAdminExpressionCondition = false;
boolean foundAmqpAdminBeanCondition = false;
boolean foundRabbitTemplateCondition = false;
int totalDecisions = 0;
for (CreatedBeanInfo item : beansBootCreated) {
for (String decision : item.getDecisions()) {
totalDecisions += 1;
if (decision.contains("RabbitConnectionFactoryCreator matched")) {
foundRabbitConnectionFactory = true;
} else if (decision.contains("OnExpressionCondition")
&& decision.contains("amqpAdmin matched due to SpEL expression")) {
foundAmqpAdminExpressionCondition = true;
} else if (decision.contains("OnBeanCondition")
&& decision.contains("amqpAdmin matched")) {
foundAmqpAdminBeanCondition = true;
} else if (decision.contains("rabbitTemplate matched")) {
foundRabbitTemplateCondition = true;
}
}
}
assertEquals(4, totalDecisions);
assertTrue(foundRabbitConnectionFactory);
assertTrue(foundAmqpAdminExpressionCondition);
assertTrue(foundAmqpAdminBeanCondition);
assertTrue(foundRabbitTemplateCondition);
}
@Test
public void verifyItGathersNegativeMatches() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(TestConfiguration2.class,
JmsTemplateAutoConfiguration.class, MultipartAutoConfiguration.class);
this.context.refresh();
AutoConfigurationReport autoconfigSettings = this.context
.getBean(AutoConfigurationReport.class);
Map<String, List<String>> negatives = autoconfigSettings.getNegativeDecisions();
boolean foundMyOwnJmsTemplateAndBackedOff = false;
boolean didNotFindMultipartConfigElement = false;
int totalNegativeDecisions = 0;
for (String key : negatives.keySet()) {
for (String decision : negatives.get(key)) {
totalNegativeDecisions += 1;
if (decision
.contains("JmsTemplateAutoConfiguration#jmsTemplate did not match")
&& decision.contains("found the following [myOwnJmsTemplate]")) {
foundMyOwnJmsTemplateAndBackedOff = true;
} else if (decision.contains("MultipartAutoConfiguration did not match")
&& decision
.contains("list['javax.servlet.MultipartConfigElement']")
&& decision.contains("found no beans")) {
didNotFindMultipartConfigElement = true;
}
}
}
// varying situations might cause multi-conditional beans to evaluate in different orders
assertTrue(totalNegativeDecisions >= 2);
assertTrue(foundMyOwnJmsTemplateAndBackedOff);
assertTrue(didNotFindMultipartConfigElement);
}
@Configuration
@EnableAutoConfigurationReport
public static class TestConfiguration {
}
@Configuration
@EnableAutoConfigurationReport
public static class TestConfiguration2 {
@Bean
JmsTemplate myOwnJmsTemplate(javax.jms.ConnectionFactory connectionFactory) {
return new JmsTemplate(connectionFactory);
}
}
}
...@@ -20,6 +20,7 @@ import java.util.Date; ...@@ -20,6 +20,7 @@ import java.util.Date;
import java.util.Map; import java.util.Map;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.report.EnableAutoConfigurationReport;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
...@@ -33,6 +34,7 @@ import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; ...@@ -33,6 +34,7 @@ import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@EnableAutoConfiguration @EnableAutoConfiguration
@EnableAutoConfigurationReport
@ComponentScan @ComponentScan
@Controller @Controller
public class SampleSecureApplication extends WebMvcConfigurerAdapter { public class SampleSecureApplication extends WebMvcConfigurerAdapter {
......
spring.thymeleaf.cache: false spring.thymeleaf.cache: false
\ No newline at end of file debug: true
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment