Add @ImportAutoConfiguration annotation for tests
Add a new `@ImportAutoConfiguration` annotation that can be used by tests that wish to selectively import certain auto-configuration classes. Also add `@AutoConfigurationPackage` so that package registration is decoupled from `@EnableAutoConfiguration`. An added benefit of the change is @EnableAutoConfigurationImportSelector can now be subclassed to provide custom annotation support if needed. Fixes gh-3660 See gh-2772
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* Indicates that the package containing the annotated class should be registered with
|
||||
* {@link AutoConfigurationPackages}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
* @see AutoConfigurationPackages
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@Import(AutoConfigurationPackages.Registrar.class)
|
||||
public @interface AutoConfigurationPackage {
|
||||
|
||||
}
|
||||
@@ -71,8 +71,8 @@ import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@Import({ EnableAutoConfigurationImportSelector.class,
|
||||
AutoConfigurationPackages.Registrar.class })
|
||||
@AutoConfigurationPackage
|
||||
@Import(EnableAutoConfigurationImportSelector.class)
|
||||
public @interface EnableAutoConfiguration {
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -41,18 +42,21 @@ import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration
|
||||
* auto-configuration}.
|
||||
* auto-configuration}. This class can also be subclassed if a custom variant of
|
||||
* {@link EnableAutoConfiguration @EnableAutoConfiguration}. is needed.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @see EnableAutoConfiguration
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@Order(Ordered.LOWEST_PRECEDENCE - 1)
|
||||
class EnableAutoConfigurationImportSelector implements DeferredImportSelector,
|
||||
public class EnableAutoConfigurationImportSelector implements DeferredImportSelector,
|
||||
BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware {
|
||||
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
@@ -66,55 +70,113 @@ class EnableAutoConfigurationImportSelector implements DeferredImportSelector,
|
||||
@Override
|
||||
public String[] selectImports(AnnotationMetadata metadata) {
|
||||
try {
|
||||
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata
|
||||
.getAnnotationAttributes(EnableAutoConfiguration.class.getName(),
|
||||
true));
|
||||
|
||||
Assert.notNull(attributes, "No auto-configuration attributes found. Is "
|
||||
+ metadata.getClassName()
|
||||
+ " annotated with @EnableAutoConfiguration?");
|
||||
|
||||
// Find all possible auto configuration classes, filtering duplicates
|
||||
List<String> factories = new ArrayList<String>(new LinkedHashSet<String>(
|
||||
SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,
|
||||
this.beanClassLoader)));
|
||||
|
||||
// Remove those specifically excluded
|
||||
Set<String> excluded = new LinkedHashSet<String>();
|
||||
excluded.addAll(Arrays.asList(attributes.getStringArray("exclude")));
|
||||
excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
|
||||
excluded.addAll(getExcludeAutoConfigurationsProperty());
|
||||
factories.removeAll(excluded);
|
||||
ConditionEvaluationReport.get(this.beanFactory).recordExclusions(excluded);
|
||||
ConditionEvaluationReport.get(this.beanFactory).recordEvaluationCandidates(
|
||||
factories);
|
||||
|
||||
// Sort
|
||||
factories = new AutoConfigurationSorter(this.resourceLoader)
|
||||
.getInPriorityOrder(factories);
|
||||
|
||||
return factories.toArray(new String[factories.size()]);
|
||||
AnnotationAttributes attributes = getAttributes(metadata);
|
||||
List<String> configurations = getCandidateConfigurations(metadata, attributes);
|
||||
configurations = removeDuplicates(configurations);
|
||||
Set<String> exclusions = getExclusions(metadata, attributes);
|
||||
configurations.removeAll(exclusions);
|
||||
configurations = sort(configurations);
|
||||
recordWithConditionEvaluationReport(configurations, exclusions);
|
||||
return configurations.toArray(new String[configurations.size()]);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the appropriate {@link AnnotationAttributes} from the
|
||||
* {@link AnnotationMetadata}. By default this method will return attributes for
|
||||
* {@link #getAnnotationClass()}.
|
||||
* @param metadata the annotation metadata
|
||||
* @return annotation attributes
|
||||
*/
|
||||
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
|
||||
String name = getAnnotationClass().getName();
|
||||
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata
|
||||
.getAnnotationAttributes(name, true));
|
||||
Assert.notNull(attributes,
|
||||
"No auto-configuration attributes found. Is " + metadata.getClassName()
|
||||
+ " annotated with " + ClassUtils.getShortName(name) + "?");
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the source annotation class used by the selector.
|
||||
* @return the annotation class
|
||||
*/
|
||||
protected Class<?> getAnnotationClass() {
|
||||
return EnableAutoConfiguration.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the auto-configuration class names that should be considered. By default
|
||||
* this method will load candidates using {@link SpringFactoriesLoader} with
|
||||
* {@link #getSpringFactoriesLoaderFactoryClass()}.
|
||||
* @param metadata the source metadata
|
||||
* @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
|
||||
* attributes}
|
||||
* @return a list of candidate configurations
|
||||
*/
|
||||
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
|
||||
AnnotationAttributes attributes) {
|
||||
return SpringFactoriesLoader.loadFactoryNames(
|
||||
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the class used by {@link SpringFactoriesLoader} to load configuration
|
||||
* candidates.
|
||||
* @return the factory class
|
||||
*/
|
||||
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
|
||||
return EnableAutoConfiguration.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return any exclusions that limit the candidate configurations.
|
||||
* @param metadata the source metadata
|
||||
* @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
|
||||
* attributes}
|
||||
* @return exclusions or an empty set
|
||||
*/
|
||||
protected Set<String> getExclusions(AnnotationMetadata metadata,
|
||||
AnnotationAttributes attributes) {
|
||||
Set<String> excluded = new LinkedHashSet<String>();
|
||||
excluded.addAll(asList(attributes, "exclude"));
|
||||
excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
|
||||
excluded.addAll(getExcludeAutoConfigurationsProperty());
|
||||
return excluded;
|
||||
}
|
||||
|
||||
private List<String> getExcludeAutoConfigurationsProperty() {
|
||||
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(this.environment,
|
||||
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(getEnvironment(),
|
||||
"spring.autoconfigure.");
|
||||
String[] exclude = resolver.getProperty("exclude", String[].class);
|
||||
return (Arrays.asList(exclude == null ? new String[0] : exclude));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
this.beanClassLoader = classLoader;
|
||||
private List<String> sort(List<String> configurations) throws IOException {
|
||||
configurations = new AutoConfigurationSorter(getResourceLoader())
|
||||
.getInPriorityOrder(configurations);
|
||||
return configurations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
private void recordWithConditionEvaluationReport(List<String> configurations,
|
||||
Collection<String> exclusions) throws IOException {
|
||||
ConditionEvaluationReport report = ConditionEvaluationReport
|
||||
.get(getBeanFactory());
|
||||
report.recordEvaluationCandidates(configurations);
|
||||
report.recordExclusions(exclusions);
|
||||
}
|
||||
|
||||
protected final <T> List<T> removeDuplicates(List<T> list) {
|
||||
return new ArrayList<T>(new LinkedHashSet<T>(list));
|
||||
}
|
||||
|
||||
protected final List<String> asList(AnnotationAttributes attributes, String name) {
|
||||
String[] value = attributes.getStringArray(name);
|
||||
return Arrays.asList(value == null ? new String[0] : value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -123,9 +185,35 @@ class EnableAutoConfigurationImportSelector implements DeferredImportSelector,
|
||||
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
|
||||
}
|
||||
|
||||
protected final ConfigurableListableBeanFactory getBeanFactory() {
|
||||
return this.beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
this.beanClassLoader = classLoader;
|
||||
}
|
||||
|
||||
protected ClassLoader getBeanClassLoader() {
|
||||
return this.beanClassLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
protected final Environment getEnvironment() {
|
||||
return this.environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
protected final ResourceLoader getResourceLoader() {
|
||||
return this.resourceLoader;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.test;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurationPackage;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* Import and apply the selected auto-configuration classes for testing purposes. Applies
|
||||
* the same ordering rules as {@code @EnableAutoConfiguration} but restricts the
|
||||
* auto-configuration classes to the specified set, rather than consulting
|
||||
* {@code spring.factories}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@AutoConfigurationPackage
|
||||
@Import(ImportAutoConfigurationImportSelector.class)
|
||||
public @interface ImportAutoConfiguration {
|
||||
|
||||
/**
|
||||
* The auto-configuration classes that should be imported.
|
||||
* @return the classes to import
|
||||
*/
|
||||
Class<?>[] value();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfigurationImportSelector;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
/**
|
||||
* Variant of {@link EnableAutoConfigurationImportSelector} for
|
||||
* {@link ImportAutoConfiguration}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ImportAutoConfigurationImportSelector extends EnableAutoConfigurationImportSelector {
|
||||
|
||||
@Override
|
||||
protected Class<?> getAnnotationClass() {
|
||||
return ImportAutoConfiguration.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getExclusions(AnnotationMetadata metadata,
|
||||
AnnotationAttributes attributes) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
|
||||
AnnotationAttributes attributes) {
|
||||
return asList(attributes, "value");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test utilities related to auto-configuration.
|
||||
*/
|
||||
package org.springframework.boot.autoconfigure.test;
|
||||
|
||||
@@ -168,4 +168,5 @@ public class EnableAutoConfigurationImportSelectorTests {
|
||||
return SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,
|
||||
getClass().getClassLoader());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@ package org.springframework.boot.autoconfigure.context;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.autoconfigure.test.ImportAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.test.EnvironmentTestUtils;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static org.hamcrest.core.Is.is;
|
||||
@@ -44,8 +46,7 @@ public class ConfigurationPropertiesAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
public void processAnnotatedBean() {
|
||||
load(new Class[] { SampleBean.class,
|
||||
ConfigurationPropertiesAutoConfiguration.class }, "foo.name:test");
|
||||
load(new Class[] { AutoConfig.class, SampleBean.class }, "foo.name:test");
|
||||
assertThat(this.context.getBean(SampleBean.class).getName(), is("test"));
|
||||
}
|
||||
|
||||
@@ -62,6 +63,12 @@ public class ConfigurationPropertiesAutoConfigurationTests {
|
||||
this.context.refresh();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ImportAutoConfiguration(ConfigurationPropertiesAutoConfiguration.class)
|
||||
static class AutoConfig {
|
||||
|
||||
}
|
||||
|
||||
@Component
|
||||
@ConfigurationProperties("foo")
|
||||
static class SampleBean {
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.test;
|
||||
|
||||
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.freemarker.FreeMarkerAutoConfiguration;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link ImportAutoConfigurationImportSelector}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ImportAutoConfigurationImportSelectorTests {
|
||||
|
||||
private final ImportAutoConfigurationImportSelector importSelector = new ImportAutoConfigurationImportSelector();
|
||||
|
||||
private final ConfigurableListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
|
||||
@Mock
|
||||
private Environment environment;
|
||||
|
||||
@Mock
|
||||
private AnnotationMetadata annotationMetadata;
|
||||
|
||||
@Mock
|
||||
private AnnotationAttributes annotationAttributes;
|
||||
|
||||
@Before
|
||||
public void configureImportSelector() {
|
||||
this.importSelector.setBeanFactory(this.beanFactory);
|
||||
this.importSelector.setEnvironment(this.environment);
|
||||
this.importSelector.setResourceLoader(new DefaultResourceLoader());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void importsAreSelected() {
|
||||
String[] value = new String[] { FreeMarkerAutoConfiguration.class.getName() };
|
||||
configureValue(value);
|
||||
String[] imports = this.importSelector.selectImports(this.annotationMetadata);
|
||||
assertThat(imports, equalTo(value));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void propertyExclusionsAreNotApplied() {
|
||||
configureValue(new String[] { FreeMarkerAutoConfiguration.class.getName() });
|
||||
this.importSelector.selectImports(this.annotationMetadata);
|
||||
verifyZeroInteractions(this.environment);
|
||||
}
|
||||
|
||||
private void configureValue(String... value) {
|
||||
String name = ImportAutoConfiguration.class.getName();
|
||||
given(this.annotationMetadata.getAnnotationAttributes(name, true)).willReturn(
|
||||
this.annotationAttributes);
|
||||
given(this.annotationAttributes.getStringArray("value")).willReturn(value);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user