Add AutoConfigurationImportFilter support

Add `AutoConfigurationImportFilter` strategy interface which can be used
to filter auto-configuration candidates before they are loaded.

See gh-7573
pull/8038/merge
Phillip Webb 8 years ago
parent 02641a8207
commit 20a20b7711

@ -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.
* <p>
* 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}:
* <ul>
* <li>{@link EnvironmentAware}</li>
* <li>{@link BeanFactoryAware }</li>
* <li>{@link BeanClassLoaderAware }</li>
* <li>{@link ResourceLoaderAware}</li>
* </ul>
*
* @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);
}

@ -24,6 +24,10 @@ import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; 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.BeansException;
import org.springframework.beans.factory.Aware; import org.springframework.beans.factory.Aware;
@ -67,6 +71,9 @@ public class AutoConfigurationImportSelector
private static final String[] NO_IMPORTS = {}; private static final String[] NO_IMPORTS = {};
private static final Log logger = LogFactory
.getLog(AutoConfigurationImportSelector.class);
private ConfigurableListableBeanFactory beanFactory; private ConfigurableListableBeanFactory beanFactory;
private Environment environment; private Environment environment;
@ -91,6 +98,7 @@ public class AutoConfigurationImportSelector
Set<String> exclusions = getExclusions(annotationMetadata, attributes); Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions); checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions); configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportListeners(configurations, exclusions); fireAutoConfigurationImportListeners(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]); return configurations.toArray(new String[configurations.size()]);
} }
@ -233,6 +241,45 @@ public class AutoConfigurationImportSelector
return configurations; return configurations;
} }
private List<String> filter(List<String> 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<String> result = new ArrayList<String>(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<String>(result);
}
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class,
this.beanClassLoader);
}
private MetadataReaderFactory getMetadataReaderFactory() { private MetadataReaderFactory getMetadataReaderFactory() {
try { try {
return getBeanFactory().getBean( return getBeanFactory().getBean(
@ -271,20 +318,20 @@ public class AutoConfigurationImportSelector
this.beanClassLoader); this.beanClassLoader);
} }
private void invokeAwareMethods(AutoConfigurationImportListener listener) { private void invokeAwareMethods(Object instance) {
if (listener instanceof Aware) { if (instance instanceof Aware) {
if (listener instanceof BeanClassLoaderAware) { if (instance instanceof BeanClassLoaderAware) {
((BeanClassLoaderAware) listener) ((BeanClassLoaderAware) instance)
.setBeanClassLoader(this.beanClassLoader); .setBeanClassLoader(this.beanClassLoader);
} }
if (listener instanceof BeanFactoryAware) { if (instance instanceof BeanFactoryAware) {
((BeanFactoryAware) listener).setBeanFactory(this.beanFactory); ((BeanFactoryAware) instance).setBeanFactory(this.beanFactory);
} }
if (listener instanceof EnvironmentAware) { if (instance instanceof EnvironmentAware) {
((EnvironmentAware) listener).setEnvironment(this.environment); ((EnvironmentAware) instance).setEnvironment(this.environment);
} }
if (listener instanceof ResourceLoaderAware) { if (instance instanceof ResourceLoaderAware) {
((ResourceLoaderAware) listener).setResourceLoader(this.resourceLoader); ((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader);
} }
} }
} }

@ -16,8 +16,11 @@
package org.springframework.boot.autoconfigure; package org.springframework.boot.autoconfigure;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
@ -25,6 +28,9 @@ import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.mockito.MockitoAnnotations; 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.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration; import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration;
@ -53,6 +59,8 @@ public class AutoConfigurationImportSelectorTests {
private final MockEnvironment environment = new MockEnvironment(); private final MockEnvironment environment = new MockEnvironment();
private List<AutoConfigurationImportFilter> filters = new ArrayList<AutoConfigurationImportFilter>();
@Rule @Rule
public ExpectedException expected = ExpectedException.none(); public ExpectedException expected = ExpectedException.none();
@ -191,6 +199,26 @@ public class AutoConfigurationImportSelectorTests {
"org.springframework.boot.autoconfigure.DoesNotExist2"); "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) { private String[] selectImports(Class<?> source) {
return this.importSelector.selectImports(new StandardAnnotationMetadata(source)); return this.importSelector.selectImports(new StandardAnnotationMetadata(source));
} }
@ -200,11 +228,16 @@ public class AutoConfigurationImportSelectorTests {
getClass().getClassLoader()); getClass().getClassLoader());
} }
private static class TestAutoConfigurationImportSelector private class TestAutoConfigurationImportSelector
extends AutoConfigurationImportSelector { extends AutoConfigurationImportSelector {
private AutoConfigurationImportEvent lastEvent; private AutoConfigurationImportEvent lastEvent;
@Override
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
return AutoConfigurationImportSelectorTests.this.filters;
}
@Override @Override
protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() { protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
return Collections.<AutoConfigurationImportListener>singletonList( return Collections.<AutoConfigurationImportListener>singletonList(
@ -225,6 +258,40 @@ public class AutoConfigurationImportSelectorTests {
} }
private static class TestAutoConfigurationImportFilter
implements AutoConfigurationImportFilter, BeanFactoryAware {
private final Set<String> nonMatching = new HashSet<String>();
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 @Configuration
private class TestConfiguration { private class TestConfiguration {

Loading…
Cancel
Save