Skip to content

Commit 20a20b7

Browse files
committed
Add AutoConfigurationImportFilter support
Add `AutoConfigurationImportFilter` strategy interface which can be used to filter auto-configuration candidates before they are loaded. See gh-7573
1 parent 02641a8 commit 20a20b7

File tree

3 files changed

+183
-11
lines changed

3 files changed

+183
-11
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure;
18+
19+
import org.springframework.beans.factory.BeanClassLoaderAware;
20+
import org.springframework.beans.factory.BeanFactoryAware;
21+
import org.springframework.context.EnvironmentAware;
22+
import org.springframework.context.ResourceLoaderAware;
23+
24+
/**
25+
* Filter that can be registered in {@code spring.factories} to limit the
26+
* auto-configuration classes considered. This interface is designed to allow fast removal
27+
* of auto-configuration classes before their bytecode is even read.
28+
* <p>
29+
* An {@link AutoConfigurationImportFilter} may implement any of the following
30+
* {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
31+
* methods will be called prior to {@link #match}:
32+
* <ul>
33+
* <li>{@link EnvironmentAware}</li>
34+
* <li>{@link BeanFactoryAware }</li>
35+
* <li>{@link BeanClassLoaderAware }</li>
36+
* <li>{@link ResourceLoaderAware}</li>
37+
* </ul>
38+
*
39+
* @author Phillip Webb
40+
* @since 1.5.0
41+
*/
42+
public interface AutoConfigurationImportFilter {
43+
44+
/**
45+
* Apply the filter to the given auto-configuration class candidates.
46+
* @param autoConfigurationClasses the auto-configuration classes being considered.
47+
* Implementations should not change the values in this array.
48+
* @param autoConfigurationMetadata access to the meta-data generated by the
49+
* auto-configure annotation processor
50+
* @return a boolean array indicating which of the auto-configuration classes should
51+
* be imported. The returned array must be the same size as the incoming
52+
* {@code autoConfigurationClasses} parameter. Entries containing {@code false} will
53+
* not be imported.
54+
*/
55+
boolean[] match(String[] autoConfigurationClasses,
56+
AutoConfigurationMetadata autoConfigurationMetadata);
57+
58+
}

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java

+57-10
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
import java.util.List;
2525
import java.util.Map;
2626
import java.util.Set;
27+
import java.util.concurrent.TimeUnit;
28+
29+
import org.apache.commons.logging.Log;
30+
import org.apache.commons.logging.LogFactory;
2731

2832
import org.springframework.beans.BeansException;
2933
import org.springframework.beans.factory.Aware;
@@ -67,6 +71,9 @@ public class AutoConfigurationImportSelector
6771

6872
private static final String[] NO_IMPORTS = {};
6973

74+
private static final Log logger = LogFactory
75+
.getLog(AutoConfigurationImportSelector.class);
76+
7077
private ConfigurableListableBeanFactory beanFactory;
7178

7279
private Environment environment;
@@ -91,6 +98,7 @@ public String[] selectImports(AnnotationMetadata annotationMetadata) {
9198
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
9299
checkExcludedClasses(configurations, exclusions);
93100
configurations.removeAll(exclusions);
101+
configurations = filter(configurations, autoConfigurationMetadata);
94102
fireAutoConfigurationImportListeners(configurations, exclusions);
95103
return configurations.toArray(new String[configurations.size()]);
96104
}
@@ -233,6 +241,45 @@ private List<String> sort(List<String> configurations,
233241
return configurations;
234242
}
235243

244+
private List<String> filter(List<String> configurations,
245+
AutoConfigurationMetadata autoConfigurationMetadata) {
246+
long startTime = System.nanoTime();
247+
String[] candidates = configurations.toArray(new String[configurations.size()]);
248+
boolean[] skip = new boolean[candidates.length];
249+
boolean skipped = false;
250+
for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
251+
invokeAwareMethods(filter);
252+
boolean[] match = filter.match(candidates, autoConfigurationMetadata);
253+
for (int i = 0; i < match.length; i++) {
254+
if (!match[i]) {
255+
skip[i] = true;
256+
skipped = true;
257+
}
258+
}
259+
}
260+
if (!skipped) {
261+
return configurations;
262+
}
263+
List<String> result = new ArrayList<String>(candidates.length);
264+
for (int i = 0; i < candidates.length; i++) {
265+
if (!skip[i]) {
266+
result.add(candidates[i]);
267+
}
268+
}
269+
if (logger.isTraceEnabled()) {
270+
int numberFiltered = configurations.size() - result.size();
271+
logger.trace("Filtered " + numberFiltered + " auto configuration class in "
272+
+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
273+
+ " ms");
274+
}
275+
return new ArrayList<String>(result);
276+
}
277+
278+
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
279+
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class,
280+
this.beanClassLoader);
281+
}
282+
236283
private MetadataReaderFactory getMetadataReaderFactory() {
237284
try {
238285
return getBeanFactory().getBean(
@@ -271,20 +318,20 @@ protected List<AutoConfigurationImportListener> getAutoConfigurationImportListen
271318
this.beanClassLoader);
272319
}
273320

274-
private void invokeAwareMethods(AutoConfigurationImportListener listener) {
275-
if (listener instanceof Aware) {
276-
if (listener instanceof BeanClassLoaderAware) {
277-
((BeanClassLoaderAware) listener)
321+
private void invokeAwareMethods(Object instance) {
322+
if (instance instanceof Aware) {
323+
if (instance instanceof BeanClassLoaderAware) {
324+
((BeanClassLoaderAware) instance)
278325
.setBeanClassLoader(this.beanClassLoader);
279326
}
280-
if (listener instanceof BeanFactoryAware) {
281-
((BeanFactoryAware) listener).setBeanFactory(this.beanFactory);
327+
if (instance instanceof BeanFactoryAware) {
328+
((BeanFactoryAware) instance).setBeanFactory(this.beanFactory);
282329
}
283-
if (listener instanceof EnvironmentAware) {
284-
((EnvironmentAware) listener).setEnvironment(this.environment);
330+
if (instance instanceof EnvironmentAware) {
331+
((EnvironmentAware) instance).setEnvironment(this.environment);
285332
}
286-
if (listener instanceof ResourceLoaderAware) {
287-
((ResourceLoaderAware) listener).setResourceLoader(this.resourceLoader);
333+
if (instance instanceof ResourceLoaderAware) {
334+
((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader);
288335
}
289336
}
290337
}

spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationImportSelectorTests.java

+68-1
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,21 @@
1616

1717
package org.springframework.boot.autoconfigure;
1818

19+
import java.util.ArrayList;
1920
import java.util.Collections;
21+
import java.util.HashSet;
2022
import java.util.List;
23+
import java.util.Set;
2124

2225
import org.junit.Before;
2326
import org.junit.Rule;
2427
import org.junit.Test;
2528
import org.junit.rules.ExpectedException;
2629
import org.mockito.MockitoAnnotations;
2730

31+
import org.springframework.beans.BeansException;
32+
import org.springframework.beans.factory.BeanFactory;
33+
import org.springframework.beans.factory.BeanFactoryAware;
2834
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2935
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
3036
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration;
@@ -53,6 +59,8 @@ public class AutoConfigurationImportSelectorTests {
5359

5460
private final MockEnvironment environment = new MockEnvironment();
5561

62+
private List<AutoConfigurationImportFilter> filters = new ArrayList<AutoConfigurationImportFilter>();
63+
5664
@Rule
5765
public ExpectedException expected = ExpectedException.none();
5866

@@ -191,6 +199,26 @@ public void nameAndPropertyExclusionsWhenNotPresentOnClasspathShouldNotThrowExce
191199
"org.springframework.boot.autoconfigure.DoesNotExist2");
192200
}
193201

202+
@Test
203+
public void filterShouldFilterImports() throws Exception {
204+
String[] defaultImports = selectImports(BasicEnableAutoConfiguration.class);
205+
this.filters.add(new TestAutoConfigurationImportFilter(defaultImports, 1));
206+
this.filters.add(new TestAutoConfigurationImportFilter(defaultImports, 3, 4));
207+
String[] filtered = selectImports(BasicEnableAutoConfiguration.class);
208+
assertThat(filtered).hasSize(defaultImports.length - 3);
209+
assertThat(filtered).doesNotContain(defaultImports[1], defaultImports[3],
210+
defaultImports[4]);
211+
}
212+
213+
@Test
214+
public void filterShouldSupportAware() throws Exception {
215+
TestAutoConfigurationImportFilter filter = new TestAutoConfigurationImportFilter(
216+
new String[] {});
217+
this.filters.add(filter);
218+
selectImports(BasicEnableAutoConfiguration.class);
219+
assertThat(filter.getBeanFactory()).isEqualTo(this.beanFactory);
220+
}
221+
194222
private String[] selectImports(Class<?> source) {
195223
return this.importSelector.selectImports(new StandardAnnotationMetadata(source));
196224
}
@@ -200,11 +228,16 @@ private List<String> getAutoConfigurationClassNames() {
200228
getClass().getClassLoader());
201229
}
202230

203-
private static class TestAutoConfigurationImportSelector
231+
private class TestAutoConfigurationImportSelector
204232
extends AutoConfigurationImportSelector {
205233

206234
private AutoConfigurationImportEvent lastEvent;
207235

236+
@Override
237+
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
238+
return AutoConfigurationImportSelectorTests.this.filters;
239+
}
240+
208241
@Override
209242
protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
210243
return Collections.<AutoConfigurationImportListener>singletonList(
@@ -225,6 +258,40 @@ public AutoConfigurationImportEvent getLastEvent() {
225258

226259
}
227260

261+
private static class TestAutoConfigurationImportFilter
262+
implements AutoConfigurationImportFilter, BeanFactoryAware {
263+
264+
private final Set<String> nonMatching = new HashSet<String>();
265+
266+
private BeanFactory beanFactory;
267+
268+
TestAutoConfigurationImportFilter(String[] configurations, int... nonMatching) {
269+
for (int i : nonMatching) {
270+
this.nonMatching.add(configurations[i]);
271+
}
272+
}
273+
274+
@Override
275+
public boolean[] match(String[] autoConfigurationClasses,
276+
AutoConfigurationMetadata autoConfigurationMetadata) {
277+
boolean[] result = new boolean[autoConfigurationClasses.length];
278+
for (int i = 0; i < result.length; i++) {
279+
result[i] = !this.nonMatching.contains(autoConfigurationClasses[i]);
280+
}
281+
return result;
282+
}
283+
284+
@Override
285+
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
286+
this.beanFactory = beanFactory;
287+
}
288+
289+
public BeanFactory getBeanFactory() {
290+
return this.beanFactory;
291+
}
292+
293+
}
294+
228295
@Configuration
229296
private class TestConfiguration {
230297

0 commit comments

Comments
 (0)