Commit 0def4477 authored by Dave Syer's avatar Dave Syer

More care required getting beans early in lifecycle

parent 0c79c891
...@@ -20,37 +20,39 @@ import org.springframework.boot.autoconfigure.condition.Outcome; ...@@ -20,37 +20,39 @@ import org.springframework.boot.autoconfigure.condition.Outcome;
/** /**
* Collects details about decision made during autoconfiguration (pass or fail) * Collects details about decision made during autoconfiguration (pass or fail)
* *
* @author Greg Turnquist * @author Greg Turnquist
*/ */
public class AutoConfigurationDecision { public class AutoConfigurationDecision {
private final String message; private final String message;
private final String classOrMethodName; private final String classOrMethodName;
private final Outcome outcome; private final Outcome outcome;
public AutoConfigurationDecision(String message, String classOrMethodName, Outcome outcome) { public AutoConfigurationDecision(String message, String classOrMethodName,
Outcome outcome) {
this.message = message; this.message = message;
this.classOrMethodName = classOrMethodName; this.classOrMethodName = classOrMethodName;
this.outcome = outcome; this.outcome = outcome;
} }
public String getMessage() { public String getMessage() {
return message; return this.message;
} }
public String getClassOrMethodName() { public String getClassOrMethodName() {
return classOrMethodName; return this.classOrMethodName;
} }
public Outcome getOutcome() { public Outcome getOutcome() {
return outcome; return this.outcome;
} }
@Override @Override
public String toString() { public String toString() {
return "AutoConfigurationDecision{" + "message='" + message + '\'' return "AutoConfigurationDecision{" + "message='" + this.message + '\''
+ ", classOrMethodName='" + classOrMethodName + '\'' + ", outcome=" + ", classOrMethodName='" + this.classOrMethodName + '\'' + ", outcome="
+ outcome + '}'; + this.outcome + '}';
} }
@Override @Override
...@@ -62,10 +64,10 @@ public class AutoConfigurationDecision { ...@@ -62,10 +64,10 @@ public class AutoConfigurationDecision {
AutoConfigurationDecision decision = (AutoConfigurationDecision) o; AutoConfigurationDecision decision = (AutoConfigurationDecision) o;
if (message != null ? !message.equals(decision.message) if (this.message != null ? !this.message.equals(decision.message)
: decision.message != null) : decision.message != null)
return false; return false;
if (outcome != null ? !outcome.equals(decision.outcome) if (this.outcome != null ? !this.outcome.equals(decision.outcome)
: decision.outcome != null) : decision.outcome != null)
return false; return false;
...@@ -74,8 +76,8 @@ public class AutoConfigurationDecision { ...@@ -74,8 +76,8 @@ public class AutoConfigurationDecision {
@Override @Override
public int hashCode() { public int hashCode() {
int result = message != null ? message.hashCode() : 0; int result = this.message != null ? this.message.hashCode() : 0;
result = 31 * result + (outcome != null ? outcome.hashCode() : 0); result = 31 * result + (this.outcome != null ? this.outcome.hashCode() : 0);
return result; return result;
} }
} }
...@@ -36,6 +36,7 @@ import org.springframework.context.ApplicationListener; ...@@ -36,6 +36,7 @@ import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.util.ClassUtils;
/** /**
* Bean used to gather autoconfiguration decisions, and then generate a collection of info * Bean used to gather autoconfiguration decisions, and then generate a collection of info
...@@ -56,7 +57,7 @@ public class AutoConfigurationReport implements ApplicationContextAware, ...@@ -56,7 +57,7 @@ public class AutoConfigurationReport implements ApplicationContextAware,
private Map<String, List<AutoConfigurationDecision>> autoconfigurationDecisions = new LinkedHashMap<String, List<AutoConfigurationDecision>>(); private Map<String, List<AutoConfigurationDecision>> autoconfigurationDecisions = new LinkedHashMap<String, List<AutoConfigurationDecision>>();
private Map<String, List<String>> positive = new LinkedHashMap<String, List<String>>(); private Map<String, List<String>> positive = new LinkedHashMap<String, List<String>>();
private Map<String, List<String>> negative = new LinkedHashMap<String, List<String>>(); private Map<String, List<String>> negative = new LinkedHashMap<String, List<String>>();
private ApplicationContext context; private ConfigurableApplicationContext context;
private boolean initialized = false; private boolean initialized = false;
public static void registerDecision(ConditionContext context, String message, public static void registerDecision(ConditionContext context, String message,
...@@ -64,7 +65,7 @@ public class AutoConfigurationReport implements ApplicationContextAware, ...@@ -64,7 +65,7 @@ public class AutoConfigurationReport implements ApplicationContextAware,
if (context.getBeanFactory().containsBeanDefinition(AUTO_CONFIGURATION_REPORT) if (context.getBeanFactory().containsBeanDefinition(AUTO_CONFIGURATION_REPORT)
|| context.getBeanFactory().containsSingleton(AUTO_CONFIGURATION_REPORT)) { || context.getBeanFactory().containsSingleton(AUTO_CONFIGURATION_REPORT)) {
AutoConfigurationReport autoconfigurationReport = context.getBeanFactory() AutoConfigurationReport autoconfigurationReport = context.getBeanFactory()
.getBean(AutoConfigurationReport.class); .getBean(AUTO_CONFIGURATION_REPORT, AutoConfigurationReport.class);
autoconfigurationReport.registerDecision(message, classOrMethodName, outcome); autoconfigurationReport.registerDecision(message, classOrMethodName, outcome);
} }
} }
...@@ -120,7 +121,7 @@ public class AutoConfigurationReport implements ApplicationContextAware, ...@@ -120,7 +121,7 @@ public class AutoConfigurationReport implements ApplicationContextAware,
@Override @Override
public void setApplicationContext(ApplicationContext applicationContext) public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException { throws BeansException {
this.context = applicationContext; this.context = (ConfigurableApplicationContext) applicationContext;
} }
@Override @Override
...@@ -133,17 +134,20 @@ public class AutoConfigurationReport implements ApplicationContextAware, ...@@ -133,17 +134,20 @@ public class AutoConfigurationReport implements ApplicationContextAware,
synchronized (this) { synchronized (this) {
if (!this.initialized) { if (!this.initialized) {
this.initialized = true; this.initialized = true;
splitDecisionsIntoPositiveAndNegative(); try {
scanPositiveDecisionsForBeansBootCreated(); splitDecisionsIntoPositiveAndNegative();
if (this.context.getEnvironment().getProperty("debug", Boolean.class, scanPositiveDecisionsForBeansBootCreated();
false)) { }
logger.info("Created beans:"); finally {
for (CreatedBeanInfo info : this.beansCreated) { if (shouldLogReport()) {
logger.info(info); logger.info("Created beans:");
} for (CreatedBeanInfo info : this.beansCreated) {
logger.info("Negative decisions:"); logger.info(info);
for (String key : this.negative.keySet()) { }
logger.info(key + ": " + this.negative.get(key)); logger.info("Negative decisions:");
for (String key : this.negative.keySet()) {
logger.info(key + ": " + this.negative.get(key));
}
} }
} }
} }
...@@ -151,6 +155,11 @@ public class AutoConfigurationReport implements ApplicationContextAware, ...@@ -151,6 +155,11 @@ public class AutoConfigurationReport implements ApplicationContextAware,
} }
} }
private boolean shouldLogReport() {
return this.context.getEnvironment().getProperty("debug", Boolean.class, false)
|| !this.context.isActive();
}
/** /**
* Scan the list of {@link AutoConfigurationDecision}'s, and if all outcomes true, * Scan the list of {@link AutoConfigurationDecision}'s, and if all outcomes true,
* then put it on the positive list. Otherwise, put it on the negative list. * then put it on the positive list. Otherwise, put it on the negative list.
...@@ -194,21 +203,36 @@ public class AutoConfigurationReport implements ApplicationContextAware, ...@@ -194,21 +203,36 @@ public class AutoConfigurationReport implements ApplicationContextAware,
for (AutoConfigurationDecision decision : this.autoconfigurationDecisions for (AutoConfigurationDecision decision : this.autoconfigurationDecisions
.get(key)) { .get(key)) {
for (String beanName : this.context.getBeanDefinitionNames()) { for (String beanName : this.context.getBeanDefinitionNames()) {
Object bean = this.context.getBean(beanName); Object bean = null;
if (decision.getMessage().contains(beanName) if (decision.getMessage().contains(beanName)
&& decision.getMessage().contains("matched")) { && decision.getMessage().contains("matched")) {
boolean anyMethodsAreBeans = false; try {
for (Method method : bean.getClass().getMethods()) { bean = this.context.getBean(beanName);
if (this.context.containsBean(method.getName())) { boolean anyMethodsAreBeans = false;
this.beansCreated.add(new CreatedBeanInfo(method for (Method method : bean.getClass().getMethods()) {
.getName(), method.getReturnType(), this.positive if (this.context.containsBean(method.getName())) {
.get(key))); this.beansCreated.add(new CreatedBeanInfo(method
anyMethodsAreBeans = true; .getName(), method.getReturnType(),
this.positive.get(key)));
anyMethodsAreBeans = true;
}
} }
}
if (!anyMethodsAreBeans) { if (!anyMethodsAreBeans) {
this.beansCreated.add(new CreatedBeanInfo(beanName, bean, this.beansCreated.add(new CreatedBeanInfo(beanName, bean
.getClass(), this.positive.get(key)));
}
}
catch (RuntimeException e) {
Class<?> type = null;
ConfigurableApplicationContext configurable = this.context;
String beanClassName = configurable.getBeanFactory()
.getBeanDefinition(beanName).getBeanClassName();
if (beanClassName != null) {
type = ClassUtils.resolveClassName(beanClassName,
configurable.getClassLoader());
}
this.beansCreated.add(new CreatedBeanInfo(beanName, type,
this.positive.get(key))); this.positive.get(key)));
} }
} }
......
...@@ -29,12 +29,6 @@ public class CreatedBeanInfo { ...@@ -29,12 +29,6 @@ public class CreatedBeanInfo {
private final Class<?> type; private final Class<?> type;
private final List<String> decisions; private final List<String> decisions;
public CreatedBeanInfo(String beanName, Object bean, List<String> decisions) {
this.name = beanName;
this.type = bean.getClass();
this.decisions = decisions;
}
public CreatedBeanInfo(String beanName, Class<?> declaredBeanType, public CreatedBeanInfo(String beanName, Class<?> declaredBeanType,
List<String> decisions) { List<String> decisions) {
this.name = beanName; this.name = beanName;
......
...@@ -168,7 +168,7 @@ public class SpringApplication { ...@@ -168,7 +168,7 @@ public class SpringApplication {
private boolean webEnvironment; private boolean webEnvironment;
private List<ApplicationContextInitializer<?>> initializers; private Collection<ApplicationContextInitializer<?>> initializers;
private Map<String, Object> defaultProperties; private Map<String, Object> defaultProperties;
...@@ -207,7 +207,7 @@ public class SpringApplication { ...@@ -207,7 +207,7 @@ public class SpringApplication {
this.initialSources.addAll(Arrays.asList(sources)); this.initialSources.addAll(Arrays.asList(sources));
} }
this.webEnvironment = deduceWebEnvironment(); this.webEnvironment = deduceWebEnvironment();
this.initializers = new ArrayList<ApplicationContextInitializer<?>>(); this.initializers = new LinkedHashSet<ApplicationContextInitializer<?>>();
this.initializers.addAll(getSpringFactoriesApplicationContextInitializers()); this.initializers.addAll(getSpringFactoriesApplicationContextInitializers());
this.mainApplicationClass = deduceMainApplicationClass(); this.mainApplicationClass = deduceMainApplicationClass();
} }
...@@ -716,15 +716,12 @@ public class SpringApplication { ...@@ -716,15 +716,12 @@ public class SpringApplication {
} }
/** /**
* Returns a mutable list of the {@link ApplicationContextInitializer}s that will be * Returns readonly list of the {@link ApplicationContextInitializer}s that will be
* applied to the Spring {@link ApplicationContext}. * applied to the Spring {@link ApplicationContext}.
* @return the initializers * @return the initializers
*/ */
public List<ApplicationContextInitializer<?>> getInitializers() { public List<ApplicationContextInitializer<?>> getInitializers() {
List<ApplicationContextInitializer<?>> initializers = new ArrayList<ApplicationContextInitializer<?>>( return new ArrayList<ApplicationContextInitializer<?>>(this.initializers);
getSpringFactoriesApplicationContextInitializers());
initializers.addAll(this.initializers);
return initializers;
} }
/** /**
......
...@@ -70,6 +70,7 @@ public class SpringApplicationBuilder { ...@@ -70,6 +70,7 @@ public class SpringApplicationBuilder {
private Map<String, Object> defaultProperties = new LinkedHashMap<String, Object>(); private Map<String, Object> defaultProperties = new LinkedHashMap<String, Object>();
private ConfigurableEnvironment environment; private ConfigurableEnvironment environment;
private Set<String> additionalProfiles = new LinkedHashSet<String>(); private Set<String> additionalProfiles = new LinkedHashSet<String>();
private Set<ApplicationContextInitializer<?>> initializers = new LinkedHashSet<ApplicationContextInitializer<?>>();
public SpringApplicationBuilder(Object... sources) { public SpringApplicationBuilder(Object... sources) {
this.application = new SpringApplication(sources); this.application = new SpringApplication(sources);
...@@ -466,12 +467,13 @@ public class SpringApplicationBuilder { ...@@ -466,12 +467,13 @@ public class SpringApplicationBuilder {
Set<ApplicationContextInitializer<?>> target = new LinkedHashSet<ApplicationContextInitializer<?>>(); Set<ApplicationContextInitializer<?>> target = new LinkedHashSet<ApplicationContextInitializer<?>>();
if (prepend) { if (prepend) {
target.addAll(Arrays.asList(initializers)); target.addAll(Arrays.asList(initializers));
target.addAll(this.application.getInitializers()); target.addAll(this.initializers);
} }
else { else {
target.addAll(this.application.getInitializers()); target.addAll(this.initializers);
target.addAll(Arrays.asList(initializers)); target.addAll(Arrays.asList(initializers));
} }
this.initializers = target;
this.application.setInitializers(target); this.application.setInitializers(target);
} }
......
...@@ -34,6 +34,7 @@ import org.springframework.util.StringUtils; ...@@ -34,6 +34,7 @@ import org.springframework.util.StringUtils;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
...@@ -169,6 +170,14 @@ public class SpringApplicationBuilderTests { ...@@ -169,6 +170,14 @@ public class SpringApplicationBuilderTests {
any(ApplicationContext.class)); any(ApplicationContext.class));
} }
@Test
public void initializersCreatedOnce() throws Exception {
SpringApplicationBuilder application = new SpringApplicationBuilder(
ExampleConfig.class).web(false);
this.context = application.run();
assertEquals(7, application.application().getInitializers().size());
}
@Configuration @Configuration
static class ExampleConfig { static class ExampleConfig {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment