diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportFilter.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportFilter.java new file mode 100644 index 00000000000..38f102038da --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportFilter.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2017 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 org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.context.EnvironmentAware; +import org.springframework.context.ResourceLoaderAware; + +/** + * Filter that can be registered in {@code spring.factories} to limit the + * auto-configuration classes considered. This interface is designed to allow fast removal + * of auto-configuration classes before their bytecode is even read. + *

+ * An {@link AutoConfigurationImportFilter} may implement any of the following + * {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective + * methods will be called prior to {@link #match}: + *

+ * + * @author Phillip Webb + * @since 1.5.0 + */ +public interface AutoConfigurationImportFilter { + + /** + * Apply the filter to the given auto-configuration class candidates. + * @param autoConfigurationClasses the auto-configuration classes being considered. + * Implementations should not change the values in this array. + * @param autoConfigurationMetadata access to the meta-data generated by the + * auto-configure annotation processor + * @return a boolean array indicating which of the auto-configuration classes should + * be imported. The returned array must be the same size as the incoming + * {@code autoConfigurationClasses} parameter. Entries containing {@code false} will + * not be imported. + */ + boolean[] match(String[] autoConfigurationClasses, + AutoConfigurationMetadata autoConfigurationMetadata); + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java index 034d5bf3b47..8d51e1111d4 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java @@ -24,6 +24,10 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.Aware; @@ -67,6 +71,9 @@ public class AutoConfigurationImportSelector private static final String[] NO_IMPORTS = {}; + private static final Log logger = LogFactory + .getLog(AutoConfigurationImportSelector.class); + private ConfigurableListableBeanFactory beanFactory; private Environment environment; @@ -91,6 +98,7 @@ public class AutoConfigurationImportSelector Set exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); + configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportListeners(configurations, exclusions); return configurations.toArray(new String[configurations.size()]); } @@ -233,6 +241,45 @@ public class AutoConfigurationImportSelector return configurations; } + private List filter(List configurations, + AutoConfigurationMetadata autoConfigurationMetadata) { + long startTime = System.nanoTime(); + String[] candidates = configurations.toArray(new String[configurations.size()]); + boolean[] skip = new boolean[candidates.length]; + boolean skipped = false; + for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) { + invokeAwareMethods(filter); + boolean[] match = filter.match(candidates, autoConfigurationMetadata); + for (int i = 0; i < match.length; i++) { + if (!match[i]) { + skip[i] = true; + skipped = true; + } + } + } + if (!skipped) { + return configurations; + } + List result = new ArrayList(candidates.length); + for (int i = 0; i < candidates.length; i++) { + if (!skip[i]) { + result.add(candidates[i]); + } + } + if (logger.isTraceEnabled()) { + int numberFiltered = configurations.size() - result.size(); + logger.trace("Filtered " + numberFiltered + " auto configuration class in " + + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + + " ms"); + } + return new ArrayList(result); + } + + protected List getAutoConfigurationImportFilters() { + return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, + this.beanClassLoader); + } + private MetadataReaderFactory getMetadataReaderFactory() { try { return getBeanFactory().getBean( @@ -271,20 +318,20 @@ public class AutoConfigurationImportSelector this.beanClassLoader); } - private void invokeAwareMethods(AutoConfigurationImportListener listener) { - if (listener instanceof Aware) { - if (listener instanceof BeanClassLoaderAware) { - ((BeanClassLoaderAware) listener) + private void invokeAwareMethods(Object instance) { + if (instance instanceof Aware) { + if (instance instanceof BeanClassLoaderAware) { + ((BeanClassLoaderAware) instance) .setBeanClassLoader(this.beanClassLoader); } - if (listener instanceof BeanFactoryAware) { - ((BeanFactoryAware) listener).setBeanFactory(this.beanFactory); + if (instance instanceof BeanFactoryAware) { + ((BeanFactoryAware) instance).setBeanFactory(this.beanFactory); } - if (listener instanceof EnvironmentAware) { - ((EnvironmentAware) listener).setEnvironment(this.environment); + if (instance instanceof EnvironmentAware) { + ((EnvironmentAware) instance).setEnvironment(this.environment); } - if (listener instanceof ResourceLoaderAware) { - ((ResourceLoaderAware) listener).setResourceLoader(this.resourceLoader); + if (instance instanceof ResourceLoaderAware) { + ((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader); } } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelectorTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelectorTests.java index cd703994748..9be2c9ad08f 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelectorTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelectorTests.java @@ -16,8 +16,11 @@ package org.springframework.boot.autoconfigure; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.junit.Before; import org.junit.Rule; @@ -25,6 +28,9 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.MockitoAnnotations; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration; @@ -53,6 +59,8 @@ public class AutoConfigurationImportSelectorTests { private final MockEnvironment environment = new MockEnvironment(); + private List filters = new ArrayList(); + @Rule public ExpectedException expected = ExpectedException.none(); @@ -191,6 +199,26 @@ public class AutoConfigurationImportSelectorTests { "org.springframework.boot.autoconfigure.DoesNotExist2"); } + @Test + public void filterShouldFilterImports() throws Exception { + String[] defaultImports = selectImports(BasicEnableAutoConfiguration.class); + this.filters.add(new TestAutoConfigurationImportFilter(defaultImports, 1)); + this.filters.add(new TestAutoConfigurationImportFilter(defaultImports, 3, 4)); + String[] filtered = selectImports(BasicEnableAutoConfiguration.class); + assertThat(filtered).hasSize(defaultImports.length - 3); + assertThat(filtered).doesNotContain(defaultImports[1], defaultImports[3], + defaultImports[4]); + } + + @Test + public void filterShouldSupportAware() throws Exception { + TestAutoConfigurationImportFilter filter = new TestAutoConfigurationImportFilter( + new String[] {}); + this.filters.add(filter); + selectImports(BasicEnableAutoConfiguration.class); + assertThat(filter.getBeanFactory()).isEqualTo(this.beanFactory); + } + private String[] selectImports(Class source) { return this.importSelector.selectImports(new StandardAnnotationMetadata(source)); } @@ -200,11 +228,16 @@ public class AutoConfigurationImportSelectorTests { getClass().getClassLoader()); } - private static class TestAutoConfigurationImportSelector + private class TestAutoConfigurationImportSelector extends AutoConfigurationImportSelector { private AutoConfigurationImportEvent lastEvent; + @Override + protected List getAutoConfigurationImportFilters() { + return AutoConfigurationImportSelectorTests.this.filters; + } + @Override protected List getAutoConfigurationImportListeners() { return Collections.singletonList( @@ -225,6 +258,40 @@ public class AutoConfigurationImportSelectorTests { } + private static class TestAutoConfigurationImportFilter + implements AutoConfigurationImportFilter, BeanFactoryAware { + + private final Set nonMatching = new HashSet(); + + private BeanFactory beanFactory; + + TestAutoConfigurationImportFilter(String[] configurations, int... nonMatching) { + for (int i : nonMatching) { + this.nonMatching.add(configurations[i]); + } + } + + @Override + public boolean[] match(String[] autoConfigurationClasses, + AutoConfigurationMetadata autoConfigurationMetadata) { + boolean[] result = new boolean[autoConfigurationClasses.length]; + for (int i = 0; i < result.length; i++) { + result[i] = !this.nonMatching.contains(autoConfigurationClasses[i]); + } + return result; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + public BeanFactory getBeanFactory() { + return this.beanFactory; + } + + } + @Configuration private class TestConfiguration {