Remove dependency from root to condition

Remove the slightly unusual dependency from the root autoconfigure
pacakge to `condition`. Prior to this commit the link was required in
ordere to populate the `ConditionEvaluationReport`. We now introduce
a `AutoConfigurationImportListener` strategy that allows anyone to
listen for AutoConfigurationImportEvents. The listener implementation
is now used to update the ConditionEvaluationReport.

Fixes gh-8073
This commit is contained in:
Phillip Webb 2017-01-22 21:31:07 -08:00
parent b225b7f33a
commit 9650668291
8 changed files with 416 additions and 52 deletions

View File

@ -0,0 +1,61 @@
/*
* 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 java.util.Collections;
import java.util.EventObject;
import java.util.List;
import java.util.Set;
/**
* Event fired when auto-configuration classes are imported.
*
* @author Phillip Webb
* @since 1.5.0
*/
public class AutoConfigurationImportEvent extends EventObject {
private final List<String> candidateConfigurations;
private final Set<String> exclusions;
public AutoConfigurationImportEvent(Object source,
List<String> candidateConfigurations, Set<String> exclusions) {
super(source);
this.candidateConfigurations = Collections
.unmodifiableList(candidateConfigurations);
this.exclusions = Collections.unmodifiableSet(exclusions);
}
/**
* Return the auto-configuration candidate configurations that are going to be
* imported.
* @return the configurations the auto-configuration candidates
*/
public List<String> getCandidateConfigurations() {
return this.candidateConfigurations;
}
/**
* Return the exclusions that were applied.
* @return the exclusions the exclusions applied
*/
public Set<String> getExclusions() {
return this.exclusions;
}
}

View File

@ -0,0 +1,52 @@
/*
* 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 java.util.EventListener;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
/**
* Listener that can be registered with {@code spring.factories} to receive details of
* imported auto-configurations.
* <p>
* An {@link AutoConfigurationImportListener} may implement any of the following
* {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
* methods will be called prior to
* {@link #onAutoConfigurationImportEvent(AutoConfigurationImportEvent)}:
* <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 AutoConfigurationImportListener extends EventListener {
/**
* Handle an auto-configuration import event.
* @param event the event to respond to
*/
void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event);
}

View File

@ -19,18 +19,17 @@ 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;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
import org.springframework.boot.bind.PropertySourcesPropertyValues;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.boot.bind.RelaxedPropertyResolver;
@ -89,7 +88,7 @@ public class AutoConfigurationImportSelector
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = sort(configurations);
recordWithConditionEvaluationReport(configurations, exclusions);
fireAutoConfigurationImportListeners(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
@ -98,11 +97,6 @@ public class AutoConfigurationImportSelector
}
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass().equals(AutoConfigurationImportSelector.class)) {
return this.environment.getProperty(
EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
true);
}
return true;
}
@ -236,14 +230,6 @@ public class AutoConfigurationImportSelector
}
}
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));
}
@ -253,6 +239,42 @@ public class AutoConfigurationImportSelector
return Arrays.asList(value == null ? new String[0] : value);
}
private void fireAutoConfigurationImportListeners(List<String> configurations,
Set<String> exclusions) {
List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
if (!listeners.isEmpty()) {
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this,
configurations, exclusions);
for (AutoConfigurationImportListener listener : listeners) {
invokeAwareMethods(listener);
listener.onAutoConfigurationImportEvent(event);
}
}
}
protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class,
this.beanClassLoader);
}
private void invokeAwareMethods(AutoConfigurationImportListener listener) {
if (listener instanceof Aware) {
if (listener instanceof BeanClassLoaderAware) {
((BeanClassLoaderAware) listener)
.setBeanClassLoader(this.beanClassLoader);
}
if (listener instanceof BeanFactoryAware) {
((BeanFactoryAware) listener).setBeanFactory(this.beanFactory);
}
if (listener instanceof EnvironmentAware) {
((EnvironmentAware) listener).setEnvironment(this.environment);
}
if (listener instanceof ResourceLoaderAware) {
((ResourceLoaderAware) listener).setResourceLoader(this.resourceLoader);
}
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory);

View File

@ -0,0 +1,53 @@
/*
* 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.condition;
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.boot.autoconfigure.AutoConfigurationImportEvent;
import org.springframework.boot.autoconfigure.AutoConfigurationImportListener;
/**
* {@link AutoConfigurationImportListener} to record results with the
* {@link ConditionEvaluationReport}.
*
* @author Phillip Webb
*/
class ConditionEvaluationReportAutoConfigurationImportListener
implements AutoConfigurationImportListener, BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
@Override
public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) {
if (this.beanFactory != null) {
ConditionEvaluationReport report = ConditionEvaluationReport
.get(this.beanFactory);
report.recordEvaluationCandidates(event.getCandidateConfigurations());
report.recordExclusions(event.getExclusions());
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (beanFactory instanceof ConfigurableListableBeanFactory
? (ConfigurableListableBeanFactory) beanFactory : null);
}
}

View File

@ -7,6 +7,10 @@ org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingIni
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\

View File

@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
@ -26,7 +27,6 @@ import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration;
import org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration;
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
@ -47,7 +47,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class AutoConfigurationImportSelectorTests {
private final AutoConfigurationImportSelector importSelector = new AutoConfigurationImportSelector();
private final TestAutoConfigurationImportSelector importSelector = new TestAutoConfigurationImportSelector();
private final ConfigurableListableBeanFactory beanFactory = new DefaultListableBeanFactory();
@ -69,8 +69,7 @@ public class AutoConfigurationImportSelectorTests {
String[] imports = selectImports(BasicEnableAutoConfiguration.class);
assertThat(imports).hasSameSizeAs(SpringFactoriesLoader.loadFactoryNames(
EnableAutoConfiguration.class, getClass().getClassLoader()));
assertThat(ConditionEvaluationReport.get(this.beanFactory).getExclusions())
.isEmpty();
assertThat(this.importSelector.getLastEvent().getExclusions()).isEmpty();
}
@Test
@ -78,7 +77,7 @@ public class AutoConfigurationImportSelectorTests {
String[] imports = selectImports(
EnableAutoConfigurationWithClassExclusions.class);
assertThat(imports).hasSize(getAutoConfigurationClassNames().size() - 1);
assertThat(ConditionEvaluationReport.get(this.beanFactory).getExclusions())
assertThat(this.importSelector.getLastEvent().getExclusions())
.contains(FreeMarkerAutoConfiguration.class.getName());
}
@ -86,7 +85,7 @@ public class AutoConfigurationImportSelectorTests {
public void classExclusionsAreAppliedWhenUsingSpringBootApplication() {
String[] imports = selectImports(SpringBootApplicationWithClassExclusions.class);
assertThat(imports).hasSize(getAutoConfigurationClassNames().size() - 1);
assertThat(ConditionEvaluationReport.get(this.beanFactory).getExclusions())
assertThat(this.importSelector.getLastEvent().getExclusions())
.contains(FreeMarkerAutoConfiguration.class.getName());
}
@ -95,7 +94,7 @@ public class AutoConfigurationImportSelectorTests {
String[] imports = selectImports(
EnableAutoConfigurationWithClassNameExclusions.class);
assertThat(imports).hasSize(getAutoConfigurationClassNames().size() - 1);
assertThat(ConditionEvaluationReport.get(this.beanFactory).getExclusions())
assertThat(this.importSelector.getLastEvent().getExclusions())
.contains(MustacheAutoConfiguration.class.getName());
}
@ -104,7 +103,7 @@ public class AutoConfigurationImportSelectorTests {
String[] imports = selectImports(
SpringBootApplicationWithClassNameExclusions.class);
assertThat(imports).hasSize(getAutoConfigurationClassNames().size() - 1);
assertThat(ConditionEvaluationReport.get(this.beanFactory).getExclusions())
assertThat(this.importSelector.getLastEvent().getExclusions())
.contains(MustacheAutoConfiguration.class.getName());
}
@ -114,7 +113,7 @@ public class AutoConfigurationImportSelectorTests {
FreeMarkerAutoConfiguration.class.getName());
String[] imports = selectImports(BasicEnableAutoConfiguration.class);
assertThat(imports).hasSize(getAutoConfigurationClassNames().size() - 1);
assertThat(ConditionEvaluationReport.get(this.beanFactory).getExclusions())
assertThat(this.importSelector.getLastEvent().getExclusions())
.contains(FreeMarkerAutoConfiguration.class.getName());
}
@ -125,9 +124,9 @@ public class AutoConfigurationImportSelectorTests {
+ MustacheAutoConfiguration.class.getName());
String[] imports = selectImports(BasicEnableAutoConfiguration.class);
assertThat(imports).hasSize(getAutoConfigurationClassNames().size() - 2);
assertThat(ConditionEvaluationReport.get(this.beanFactory).getExclusions())
.contains(FreeMarkerAutoConfiguration.class.getName(),
MustacheAutoConfiguration.class.getName());
assertThat(this.importSelector.getLastEvent().getExclusions()).contains(
FreeMarkerAutoConfiguration.class.getName(),
MustacheAutoConfiguration.class.getName());
}
@Test
@ -138,9 +137,9 @@ public class AutoConfigurationImportSelectorTests {
MustacheAutoConfiguration.class.getName());
String[] imports = selectImports(BasicEnableAutoConfiguration.class);
assertThat(imports).hasSize(getAutoConfigurationClassNames().size() - 2);
assertThat(ConditionEvaluationReport.get(this.beanFactory).getExclusions())
.contains(FreeMarkerAutoConfiguration.class.getName(),
MustacheAutoConfiguration.class.getName());
assertThat(this.importSelector.getLastEvent().getExclusions()).contains(
FreeMarkerAutoConfiguration.class.getName(),
MustacheAutoConfiguration.class.getName());
}
@Test
@ -150,26 +149,10 @@ public class AutoConfigurationImportSelectorTests {
String[] imports = selectImports(
EnableAutoConfigurationWithClassAndClassNameExclusions.class);
assertThat(imports).hasSize(getAutoConfigurationClassNames().size() - 3);
assertThat(ConditionEvaluationReport.get(this.beanFactory).getExclusions())
.contains(FreeMarkerAutoConfiguration.class.getName(),
MustacheAutoConfiguration.class.getName(),
ThymeleafAutoConfiguration.class.getName());
}
@Test
public void propertyOverrideSetToTrue() throws Exception {
this.environment.setProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY,
"true");
String[] imports = selectImports(BasicEnableAutoConfiguration.class);
assertThat(imports).isNotEmpty();
}
@Test
public void propertyOverrideSetToFalse() throws Exception {
this.environment.setProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY,
"false");
String[] imports = selectImports(BasicEnableAutoConfiguration.class);
assertThat(imports).isEmpty();
assertThat(this.importSelector.getLastEvent().getExclusions()).contains(
FreeMarkerAutoConfiguration.class.getName(),
MustacheAutoConfiguration.class.getName(),
ThymeleafAutoConfiguration.class.getName());
}
@Test
@ -202,7 +185,7 @@ public class AutoConfigurationImportSelectorTests {
this.environment.setProperty("spring.autoconfigure.exclude",
"org.springframework.boot.autoconfigure.DoesNotExist2");
selectImports(EnableAutoConfigurationWithAbsentClassNameExclude.class);
assertThat(ConditionEvaluationReport.get(this.beanFactory).getExclusions())
assertThat(this.importSelector.getLastEvent().getExclusions())
.containsExactlyInAnyOrder(
"org.springframework.boot.autoconfigure.DoesNotExist1",
"org.springframework.boot.autoconfigure.DoesNotExist2");
@ -217,6 +200,31 @@ public class AutoConfigurationImportSelectorTests {
getClass().getClassLoader());
}
private static class TestAutoConfigurationImportSelector
extends AutoConfigurationImportSelector {
private AutoConfigurationImportEvent lastEvent;
@Override
protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
return Collections.<AutoConfigurationImportListener>singletonList(
new AutoConfigurationImportListener() {
@Override
public void onAutoConfigurationImportEvent(
AutoConfigurationImportEvent event) {
TestAutoConfigurationImportSelector.this.lastEvent = event;
}
});
}
public AutoConfigurationImportEvent getLastEvent() {
return this.lastEvent;
}
}
@Configuration
private class TestConfiguration {

View File

@ -0,0 +1,80 @@
/*
* 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.junit.Before;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link EnableAutoConfigurationImportSelector}
*
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Madhura Bhave
*/
@SuppressWarnings("deprecation")
public class EnableAutoConfigurationImportSelectorTests {
private final EnableAutoConfigurationImportSelector importSelector = new EnableAutoConfigurationImportSelector();
private final ConfigurableListableBeanFactory beanFactory = new DefaultListableBeanFactory();
private final MockEnvironment environment = new MockEnvironment();
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.importSelector.setBeanFactory(this.beanFactory);
this.importSelector.setEnvironment(this.environment);
this.importSelector.setResourceLoader(new DefaultResourceLoader());
}
@Test
public void propertyOverrideSetToTrue() throws Exception {
this.environment.setProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY,
"true");
String[] imports = selectImports(BasicEnableAutoConfiguration.class);
assertThat(imports).isNotEmpty();
}
@Test
public void propertyOverrideSetToFalse() throws Exception {
this.environment.setProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY,
"false");
String[] imports = selectImports(BasicEnableAutoConfiguration.class);
assertThat(imports).isEmpty();
}
private String[] selectImports(Class<?> source) {
return this.importSelector.selectImports(new StandardAnnotationMetadata(source));
}
@EnableAutoConfiguration
private class BasicEnableAutoConfiguration {
}
}

View File

@ -0,0 +1,84 @@
/*
* 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.condition;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigurationImportEvent;
import org.springframework.boot.autoconfigure.AutoConfigurationImportListener;
import org.springframework.core.io.support.SpringFactoriesLoader;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConditionEvaluationReportAutoConfigurationImportListener}.
*
* @author Phillip Webb
*/
public class ConditionEvaluationReportAutoConfigurationImportListenerTests {
private ConditionEvaluationReportAutoConfigurationImportListener listener;
private final ConfigurableListableBeanFactory beanFactory = new DefaultListableBeanFactory();
@Before
public void setup() {
this.listener = new ConditionEvaluationReportAutoConfigurationImportListener();
this.listener.setBeanFactory(this.beanFactory);
}
@Test
public void shouldBeInSpringFactories() throws Exception {
List<AutoConfigurationImportListener> factories = SpringFactoriesLoader
.loadFactories(AutoConfigurationImportListener.class, null);
assertThat(factories).hasAtLeastOneElementOfType(
ConditionEvaluationReportAutoConfigurationImportListener.class);
}
@Test
public void onAutoConfigurationImportEventShouldRecordCandidates() throws Exception {
List<String> candidateConfigurations = Collections.singletonList("Test");
Set<String> exclusions = Collections.emptySet();
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this,
candidateConfigurations, exclusions);
this.listener.onAutoConfigurationImportEvent(event);
ConditionEvaluationReport report = ConditionEvaluationReport
.get(this.beanFactory);
assertThat(report.getUnconditionalClasses())
.containsExactlyElementsOf(candidateConfigurations);
}
@Test
public void onAutoConfigurationImportEventShouldRecordExclusions() throws Exception {
List<String> candidateConfigurations = Collections.emptyList();
Set<String> exclusions = Collections.singleton("Test");
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this,
candidateConfigurations, exclusions);
this.listener.onAutoConfigurationImportEvent(event);
ConditionEvaluationReport report = ConditionEvaluationReport
.get(this.beanFactory);
assertThat(report.getExclusions()).containsExactlyElementsOf(exclusions);
}
}