Add AutoConfigurationImportFilter support

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

See gh-7573
This commit is contained in:
Phillip Webb 2017-01-22 23:14:17 -08:00
parent 02641a8207
commit 20a20b7711
3 changed files with 183 additions and 11 deletions

View File

@ -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);
}

View File

@ -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<String> 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<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() {
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);
}
}
}

View File

@ -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<AutoConfigurationImportFilter> filters = new ArrayList<AutoConfigurationImportFilter>();
@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<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
return AutoConfigurationImportSelectorTests.this.filters;
}
@Override
protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
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
private class TestConfiguration {