From 8850286937562f751c9e667e79bedfc6d7b988db Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 30 Mar 2015 15:05:42 +0100 Subject: [PATCH] List excluded auto-config classes in the auto-config report and endpoint Prior to this commit, the auto-configuration report (both in its logged form and the actuator endpoint) listed the positive and negative matches but did not list the classes, if any, that the user had excluded. This commit updates the logged report and the actuator endpoint to expose a list of the excluded class names configured via the exclude attribute on @EnableAutoConfiguration. Closes gh-2085 --- .../AutoConfigurationReportEndpoint.java | 12 ++- .../AutoConfigurationReportEndpointTests.java | 7 +- ...EnableAutoConfigurationImportSelector.java | 22 +++- .../condition/ConditionEvaluationReport.java | 20 ++++ ...ConfigurationReportLoggingInitializer.java | 20 +++- ...eAutoConfigurationImportSelectorTests.java | 101 ++++++++++++++++++ ...gurationReportLoggingInitializerTests.java | 6 +- 7 files changed, 177 insertions(+), 11 deletions(-) create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/EnableAutoConfigurationImportSelectorTests.java diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java index bc3911dc7a..a8e36c4aec 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2015 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. @@ -42,6 +42,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; * @author Greg Turnquist * @author Phillip Webb * @author Dave Syer + * @author Andy Wilkinson */ @ConfigurationProperties(prefix = "endpoints.autoconfig", ignoreUnknownFields = false) public class AutoConfigurationReportEndpoint extends AbstractEndpoint { @@ -61,7 +62,7 @@ public class AutoConfigurationReportEndpoint extends AbstractEndpoint { /** * Adapts {@link ConditionEvaluationReport} to a JSON friendly structure. */ - @JsonPropertyOrder({ "positiveMatches", "negativeMatches" }) + @JsonPropertyOrder({ "positiveMatches", "negativeMatches", "exclusions" }) @JsonInclude(Include.NON_EMPTY) public static class Report { @@ -69,11 +70,14 @@ public class AutoConfigurationReportEndpoint extends AbstractEndpoint { private MultiValueMap negativeMatches; + private List exclusions; + private Report parent; public Report(ConditionEvaluationReport report) { this.positiveMatches = new LinkedMultiValueMap(); this.negativeMatches = new LinkedMultiValueMap(); + this.exclusions = report.getExclusions(); for (Map.Entry entry : report .getConditionAndOutcomesBySource().entrySet()) { add(entry.getValue().isFullMatch() ? this.positiveMatches @@ -101,6 +105,10 @@ public class AutoConfigurationReportEndpoint extends AbstractEndpoint { return this.negativeMatches; } + public List getExclusions() { + return this.exclusions; + } + public Report getParent() { return this.parent; } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpointTests.java index 4b5477fa40..af159457e4 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpointTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2015 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. @@ -16,6 +16,8 @@ package org.springframework.boot.actuate.endpoint; +import java.util.Arrays; + import javax.annotation.PostConstruct; import org.junit.Test; @@ -37,6 +39,7 @@ import static org.mockito.Mockito.mock; * * @author Greg Turnquist * @author Phillip Webb + * @author Andy Wilkinson */ public class AutoConfigurationReportEndpointTests extends AbstractEndpointTests { @@ -51,6 +54,7 @@ public class AutoConfigurationReportEndpointTests extends Report report = getEndpointBean().invoke(); assertTrue(report.getPositiveMatches().isEmpty()); assertTrue(report.getNegativeMatches().containsKey("a")); + assertTrue(report.getExclusions().contains("com.foo.Bar")); } @Configuration @@ -66,6 +70,7 @@ public class AutoConfigurationReportEndpointTests extends .getBeanFactory()); report.recordConditionEvaluation("a", mock(Condition.class), mock(ConditionOutcome.class)); + report.recordExclusions(Arrays.asList("com.foo.Bar")); } @Bean diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/EnableAutoConfigurationImportSelector.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/EnableAutoConfigurationImportSelector.java index fc5a0bde10..37075e8e68 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/EnableAutoConfigurationImportSelector.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/EnableAutoConfigurationImportSelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2015 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. @@ -22,7 +22,12 @@ import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.DeferredImportSelector; import org.springframework.core.Ordered; @@ -43,7 +48,9 @@ import org.springframework.util.Assert; */ @Order(Ordered.LOWEST_PRECEDENCE) class EnableAutoConfigurationImportSelector implements DeferredImportSelector, - BeanClassLoaderAware, ResourceLoaderAware { + BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware { + + private ConfigurableListableBeanFactory beanFactory; private ClassLoader beanClassLoader; @@ -66,7 +73,10 @@ class EnableAutoConfigurationImportSelector implements DeferredImportSelector, this.beanClassLoader))); // Remove those specifically disabled - factories.removeAll(Arrays.asList(attributes.getStringArray("exclude"))); + List excluded = new ArrayList(Arrays.asList(attributes + .getStringArray("exclude"))); + factories.removeAll(excluded); + ConditionEvaluationReport.get(this.beanFactory).recordExclusions(excluded); // Sort factories = new AutoConfigurationSorter(this.resourceLoader) @@ -89,4 +99,10 @@ class EnableAutoConfigurationImportSelector implements DeferredImportSelector, this.resourceLoader = resourceLoader; } + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory); + this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; + } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java index 0fd8d031b8..8cb7e34437 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java @@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.condition; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -53,6 +54,8 @@ public class ConditionEvaluationReport { private ConditionEvaluationReport parent; + private List exclusions = Collections.emptyList(); + /** * Private constructor. * @see #get(ConfigurableListableBeanFactory) @@ -78,6 +81,15 @@ public class ConditionEvaluationReport { this.addedAncestorOutcomes = false; } + /** + * Records the name of the classes that have been excluded from condition evaluation + * @param exclusions the names of the excluded classes + */ + public void recordExclusions(List exclusions) { + Assert.notNull(exclusions, "exclusions must not be null"); + this.exclusions = exclusions; + } + /** * Returns condition outcomes from this report, grouped by the source. * @return the condition outcomes @@ -94,6 +106,14 @@ public class ConditionEvaluationReport { return Collections.unmodifiableMap(this.outcomes); } + /** + * Returns the name of the classes that have been excluded from condition evaluation. + * @return the names of the excluded classes + */ + public List getExclusions() { + return Collections.unmodifiableList(this.exclusions); + } + private void addNoMatchOutcomeToAncestors(String source) { String prefix = source + "$"; for (Entry entry : this.outcomes.entrySet()) { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializer.java index 64fb9404f5..15e75efbf9 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializer.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializer.java @@ -53,6 +53,7 @@ import org.springframework.util.StringUtils; * @author Greg Turnquist * @author Dave Syer * @author Phillip Webb + * @author Andy Wilkinson */ public class AutoConfigurationReportLoggingInitializer implements ApplicationContextInitializer { @@ -110,13 +111,12 @@ public class AutoConfigurationReportLoggingInitializer implements + "debug logging (start with --debug)\n\n"); } if (this.logger.isDebugEnabled()) { - this.logger.debug(getLogMessage(this.report - .getConditionAndOutcomesBySource())); + this.logger.debug(getLogMessage(this.report)); } } } - private StringBuilder getLogMessage(Map outcomes) { + private StringBuilder getLogMessage(ConditionEvaluationReport report) { StringBuilder message = new StringBuilder(); message.append("\n\n\n"); message.append("=========================\n"); @@ -124,7 +124,8 @@ public class AutoConfigurationReportLoggingInitializer implements message.append("=========================\n\n\n"); message.append("Positive matches:\n"); message.append("-----------------\n"); - Map shortOutcomes = orderByName(outcomes); + Map shortOutcomes = orderByName(report + .getConditionAndOutcomesBySource()); for (Map.Entry entry : shortOutcomes.entrySet()) { if (entry.getValue().isFullMatch()) { addLogMessage(message, entry.getKey(), entry.getValue()); @@ -139,6 +140,17 @@ public class AutoConfigurationReportLoggingInitializer implements } } message.append("\n\n"); + message.append("Exclusions:\n"); + message.append("-----------\n"); + if (report.getExclusions().isEmpty()) { + message.append("\n None\n"); + } + else { + for (String exclusion : report.getExclusions()) { + message.append("\n " + exclusion + "\n"); + } + } + message.append("\n\n"); return message; } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/EnableAutoConfigurationImportSelectorTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/EnableAutoConfigurationImportSelectorTests.java new file mode 100644 index 0000000000..d9017a9b5d --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/EnableAutoConfigurationImportSelectorTests.java @@ -0,0 +1,101 @@ +/* + * Copyright 2012-2015 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 java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; +import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.core.type.AnnotationMetadata; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; + +/** + * Tests for {@link EnableAutoConfigurationImportSelector} + * + * @author Andy Wilkinson + */ +@RunWith(MockitoJUnitRunner.class) +public class EnableAutoConfigurationImportSelectorTests { + + private final EnableAutoConfigurationImportSelector importSelector = new EnableAutoConfigurationImportSelector(); + + private final ConfigurableListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + + @Mock + private AnnotationMetadata annotationMetadata; + + @Mock + private AnnotationAttributes annotationAttributes; + + @Before + public void configureImportSelector() { + this.importSelector.setBeanFactory(this.beanFactory); + this.importSelector.setResourceLoader(new DefaultResourceLoader()); + } + + @Test + public void importsAreSelected() { + configureExclusions(); + String[] imports = this.importSelector.selectImports(this.annotationMetadata); + assertThat( + imports.length, + is(equalTo(SpringFactoriesLoader.loadFactoryNames( + EnableAutoConfiguration.class, getClass().getClassLoader()) + .size()))); + assertThat(ConditionEvaluationReport.get(this.beanFactory).getExclusions(), + hasSize(0)); + } + + @Test + public void exclusionsAreApplied() { + configureExclusions(FreeMarkerAutoConfiguration.class.getName()); + String[] imports = this.importSelector.selectImports(this.annotationMetadata); + assertThat(imports.length, + is(equalTo(getAutoConfigurationClassNames().size() - 1))); + assertThat(ConditionEvaluationReport.get(this.beanFactory).getExclusions(), + contains(FreeMarkerAutoConfiguration.class.getName())); + } + + private void configureExclusions(String... exclusions) { + given( + this.annotationMetadata.getAnnotationAttributes( + EnableAutoConfiguration.class.getName(), true)).willReturn( + this.annotationAttributes); + given(this.annotationAttributes.getStringArray("exclude")).willReturn(exclusions); + } + + private List getAutoConfigurationClassNames() { + return SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, + getClass().getClassLoader()); + } +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializerTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializerTests.java index a8d3f727c4..8221caaede 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializerTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2015 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. @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.logging; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.apache.commons.logging.Log; @@ -58,6 +59,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link AutoConfigurationReportLoggingInitializer}. * * @author Phillip Webb + * @author Andy Wilkinson */ public class AutoConfigurationReportLoggingInitializerTests { @@ -161,6 +163,8 @@ public class AutoConfigurationReportLoggingInitializerTests { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); this.initializer.initialize(context); context.register(Config.class); + ConditionEvaluationReport.get(context.getBeanFactory()).recordExclusions( + Arrays.asList("com.foo.Bar")); context.refresh(); this.initializer.onApplicationEvent(new ContextRefreshedEvent(context)); for (String message : this.debugLog) {