Rework auto-configure report
Update the auto-configuration report to improve log formatting and to separate the internal report data-structure from the JSON friendly endpoint data-structure.
This commit is contained in:
parent
04fd7fdbbe
commit
dafeddca09
|
|
@ -40,11 +40,11 @@ import org.springframework.boot.actuate.metrics.InMemoryMetricRepository;
|
||||||
import org.springframework.boot.actuate.metrics.MetricRepository;
|
import org.springframework.boot.actuate.metrics.MetricRepository;
|
||||||
import org.springframework.boot.actuate.trace.InMemoryTraceRepository;
|
import org.springframework.boot.actuate.trace.InMemoryTraceRepository;
|
||||||
import org.springframework.boot.actuate.trace.TraceRepository;
|
import org.springframework.boot.actuate.trace.TraceRepository;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurationReport;
|
||||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.boot.autoconfigure.report.AutoConfigurationReport;
|
|
||||||
import org.springframework.boot.bind.PropertiesConfigurationFactory;
|
import org.springframework.boot.bind.PropertiesConfigurationFactory;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
|
||||||
|
|
@ -16,29 +16,114 @@
|
||||||
|
|
||||||
package org.springframework.boot.actuate.endpoint;
|
package org.springframework.boot.actuate.endpoint;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.autoconfigure.report.AutoConfigurationReport;
|
import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint.Report;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurationReport;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurationReport.ConditionAndOutcome;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurationReport.ConditionAndOutcomes;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Condition;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Endpoint to serve up autoconfiguration report if actuator is on the classpath.
|
* {@link Endpoint} to expose the {@link AutoConfigurationReport}.
|
||||||
*
|
*
|
||||||
* @author Greg Turnquist
|
* @author Greg Turnquist
|
||||||
|
* @author Phillip Webb
|
||||||
*/
|
*/
|
||||||
@ConfigurationProperties(name = "endpoints.autoconfigurationreport", ignoreUnknownFields = false)
|
@ConfigurationProperties(name = "endpoints.autoconfigurationreport", ignoreUnknownFields = false)
|
||||||
public class AutoConfigurationReportEndpoint extends
|
public class AutoConfigurationReportEndpoint extends AbstractEndpoint<Report> {
|
||||||
AbstractEndpoint<AutoConfigurationReport> {
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private AutoConfigurationReport autoConfigurationReport;
|
private AutoConfigurationReport autoConfigurationReport;
|
||||||
|
|
||||||
public AutoConfigurationReportEndpoint() {
|
public AutoConfigurationReportEndpoint() {
|
||||||
super("/auto");
|
super("/autoconfigurationreport");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AutoConfigurationReport invoke() {
|
public Report invoke() {
|
||||||
return this.autoConfigurationReport;
|
return new Report(this.autoConfigurationReport);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapts {@link AutoConfigurationReport} to a JSON friendly structure.
|
||||||
|
*/
|
||||||
|
@JsonPropertyOrder({ "positiveMatches", "negativeMatches" })
|
||||||
|
public static class Report {
|
||||||
|
|
||||||
|
private MultiValueMap<String, MessageAndCondition> positiveMatches;
|
||||||
|
|
||||||
|
private MultiValueMap<String, MessageAndCondition> negativeMatches;
|
||||||
|
|
||||||
|
public Report(AutoConfigurationReport report) {
|
||||||
|
this.positiveMatches = new LinkedMultiValueMap<String, MessageAndCondition>();
|
||||||
|
this.negativeMatches = new LinkedMultiValueMap<String, MessageAndCondition>();
|
||||||
|
for (Map.Entry<String, ConditionAndOutcomes> entry : report
|
||||||
|
.getConditionAndOutcomesBySource().entrySet()) {
|
||||||
|
dunno(entry.getValue().isFullMatch() ? this.positiveMatches
|
||||||
|
: this.negativeMatches, entry.getKey(), entry.getValue());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dunno(MultiValueMap<String, MessageAndCondition> map, String source,
|
||||||
|
ConditionAndOutcomes conditionAndOutcomes) {
|
||||||
|
String name = ClassUtils.getShortName(source);
|
||||||
|
for (ConditionAndOutcome conditionAndOutcome : conditionAndOutcomes) {
|
||||||
|
map.add(name, new MessageAndCondition(conditionAndOutcome));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, List<MessageAndCondition>> getPositiveMatches() {
|
||||||
|
return this.positiveMatches;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, List<MessageAndCondition>> getNegativeMatches() {
|
||||||
|
return this.negativeMatches;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapts {@link ConditionAndOutcome} to a JSON friendly structure.
|
||||||
|
*/
|
||||||
|
@JsonPropertyOrder({ "condition", "message" })
|
||||||
|
public static class MessageAndCondition {
|
||||||
|
|
||||||
|
private final String condition;
|
||||||
|
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
public MessageAndCondition(ConditionAndOutcome conditionAndOutcome) {
|
||||||
|
Condition condition = conditionAndOutcome.getCondition();
|
||||||
|
ConditionOutcome outcome = conditionAndOutcome.getOutcome();
|
||||||
|
this.condition = ClassUtils.getShortName(condition.getClass());
|
||||||
|
if (StringUtils.hasLength(outcome.getMessage())) {
|
||||||
|
this.message = outcome.getMessage();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.message = (outcome.isMatch() ? "matched" : "did not match");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCondition() {
|
||||||
|
return this.condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return this.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package org.springframework.boot.actuate.autoconfigure;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
|
||||||
import org.springframework.boot.TestUtils;
|
import org.springframework.boot.TestUtils;
|
||||||
import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint;
|
import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.BeansEndpoint;
|
import org.springframework.boot.actuate.endpoint.BeansEndpoint;
|
||||||
|
|
@ -29,7 +28,7 @@ import org.springframework.boot.actuate.endpoint.InfoEndpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
|
import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
|
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.TraceEndpoint;
|
import org.springframework.boot.actuate.endpoint.TraceEndpoint;
|
||||||
import org.springframework.boot.autoconfigure.report.AutoConfigurationReport;
|
import org.springframework.boot.autoconfigure.AutoConfigurationReport;
|
||||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
@ -66,11 +65,6 @@ public class EndpointAutoConfigurationTests {
|
||||||
assertNotNull(this.context.getBean(TraceEndpoint.class));
|
assertNotNull(this.context.getBean(TraceEndpoint.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = NoSuchBeanDefinitionException.class)
|
|
||||||
public void noAutoConfigurationAuditEndpointByDefault() {
|
|
||||||
this.context.getBean(AutoConfigurationReportEndpoint.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void autoconfigurationAuditEndpoints() {
|
public void autoconfigurationAuditEndpoints() {
|
||||||
this.context = new AnnotationConfigApplicationContext();
|
this.context = new AnnotationConfigApplicationContext();
|
||||||
|
|
|
||||||
|
|
@ -16,38 +16,62 @@
|
||||||
|
|
||||||
package org.springframework.boot.actuate.endpoint;
|
package org.springframework.boot.actuate.endpoint;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.report.AutoConfigurationReport;
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint.Report;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurationReport;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Condition;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link AutoConfigurationReportEndpoint}.
|
* Tests for {@link AutoConfigurationReportEndpoint}.
|
||||||
*
|
*
|
||||||
* @author Greg Turnquist
|
* @author Greg Turnquist
|
||||||
|
* @author Phillip Webb
|
||||||
*/
|
*/
|
||||||
public class AutoConfigurationReportEndpointTests extends
|
public class AutoConfigurationReportEndpointTests extends
|
||||||
AbstractEndpointTests<AutoConfigurationReportEndpoint> {
|
AbstractEndpointTests<AutoConfigurationReportEndpoint> {
|
||||||
|
|
||||||
public AutoConfigurationReportEndpointTests() {
|
public AutoConfigurationReportEndpointTests() {
|
||||||
super(Config.class, AutoConfigurationReportEndpoint.class, "/auto", true,
|
super(Config.class, AutoConfigurationReportEndpoint.class,
|
||||||
"endpoints.autoconfigurationreport");
|
"/autoconfigurationreport", true, "endpoints.autoconfigurationreport");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void invoke() throws Exception {
|
||||||
|
Report report = getEndpointBean().invoke();
|
||||||
|
assertTrue(report.getPositiveMatches().isEmpty());
|
||||||
|
assertTrue(report.getNegativeMatches().containsKey("a"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableConfigurationProperties
|
@EnableConfigurationProperties
|
||||||
public static class Config {
|
public static class Config {
|
||||||
|
|
||||||
@Bean
|
@Autowired
|
||||||
public AutoConfigurationReport autoConfigurationReport() {
|
private ConfigurableApplicationContext context;
|
||||||
return new AutoConfigurationReport();
|
|
||||||
|
@PostConstruct
|
||||||
|
public void setupAutoConfigurationReport() {
|
||||||
|
AutoConfigurationReport report = AutoConfigurationReport.get(this.context
|
||||||
|
.getBeanFactory());
|
||||||
|
report.recordConditionEvaluation("a", mock(Condition.class),
|
||||||
|
mock(ConditionOutcome.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public AutoConfigurationReportEndpoint endpoint() {
|
public AutoConfigurationReportEndpoint endpoint() {
|
||||||
return new AutoConfigurationReportEndpoint();
|
return new AutoConfigurationReportEndpoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2013 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.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.SortedMap;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||||
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||||
|
import org.springframework.context.annotation.Condition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records auto-configuration details for reporting and logging.
|
||||||
|
*
|
||||||
|
* @author Greg Turnquist
|
||||||
|
* @author Dave Syer
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
public class AutoConfigurationReport {
|
||||||
|
|
||||||
|
private static final String BEAN_NAME = "autoConfigurationReport";
|
||||||
|
|
||||||
|
private final SortedMap<String, ConditionAndOutcomes> outcomes = new TreeMap<String, ConditionAndOutcomes>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor.
|
||||||
|
* @see #get(ConfigurableListableBeanFactory)
|
||||||
|
*/
|
||||||
|
private AutoConfigurationReport() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record the occurrence of condition evaluation.
|
||||||
|
* @param source the source of the condition (class or method name)
|
||||||
|
* @param condition the condition evaluated
|
||||||
|
* @param outcome the condition outcome
|
||||||
|
*/
|
||||||
|
public void recordConditionEvaluation(String source, Condition condition,
|
||||||
|
ConditionOutcome outcome) {
|
||||||
|
if (!this.outcomes.containsKey(source)) {
|
||||||
|
this.outcomes.put(source, new ConditionAndOutcomes());
|
||||||
|
}
|
||||||
|
this.outcomes.get(source).add(condition, outcome);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns condition outcomes from this report, grouped by the source.
|
||||||
|
*/
|
||||||
|
public Map<String, ConditionAndOutcomes> getConditionAndOutcomesBySource() {
|
||||||
|
return Collections.unmodifiableMap(this.outcomes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain a {@link AutoConfigurationReport} for the specified bean factory.
|
||||||
|
* @param beanFactory the bean factory
|
||||||
|
* @return an existing or new {@link AutoConfigurationReport}
|
||||||
|
*/
|
||||||
|
public static AutoConfigurationReport get(ConfigurableListableBeanFactory beanFactory) {
|
||||||
|
synchronized (beanFactory) {
|
||||||
|
try {
|
||||||
|
return beanFactory.getBean(BEAN_NAME, AutoConfigurationReport.class);
|
||||||
|
}
|
||||||
|
catch (NoSuchBeanDefinitionException ex) {
|
||||||
|
AutoConfigurationReport report = new AutoConfigurationReport();
|
||||||
|
beanFactory.registerSingleton(BEAN_NAME, report);
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides access to a number of {@link ConditionAndOutcome} items.
|
||||||
|
*/
|
||||||
|
public static class ConditionAndOutcomes implements Iterable<ConditionAndOutcome> {
|
||||||
|
|
||||||
|
private List<ConditionAndOutcome> outcomes = new ArrayList<ConditionAndOutcome>();
|
||||||
|
|
||||||
|
public void add(Condition condition, ConditionOutcome outcome) {
|
||||||
|
this.outcomes.add(new ConditionAndOutcome(condition, outcome));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return {@code true} if all outcomes match.
|
||||||
|
*/
|
||||||
|
public boolean isFullMatch() {
|
||||||
|
for (ConditionAndOutcome conditionAndOutcomes : this) {
|
||||||
|
if (!conditionAndOutcomes.getOutcome().isMatch()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<ConditionAndOutcome> iterator() {
|
||||||
|
return Collections.unmodifiableList(this.outcomes).iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides access to a single {@link Condition} and {@link ConditionOutcome}.
|
||||||
|
*/
|
||||||
|
public static class ConditionAndOutcome {
|
||||||
|
|
||||||
|
private final Condition condition;
|
||||||
|
|
||||||
|
private final ConditionOutcome outcome;
|
||||||
|
|
||||||
|
public ConditionAndOutcome(Condition condition, ConditionOutcome outcome) {
|
||||||
|
this.condition = condition;
|
||||||
|
this.outcome = outcome;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Condition getCondition() {
|
||||||
|
return this.condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConditionOutcome getOutcome() {
|
||||||
|
return this.outcome;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,163 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2013 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.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.SpringApplicationErrorHandler;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurationReport.ConditionAndOutcome;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurationReport.ConditionAndOutcomes;
|
||||||
|
import org.springframework.boot.logging.LogLevel;
|
||||||
|
import org.springframework.context.ApplicationContextInitializer;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
|
import org.springframework.context.event.ContextRefreshedEvent;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ApplicationContextInitializer} and {@link SpringApplicationErrorHandler} that
|
||||||
|
* writes the {@link AutoConfigurationReport} to the log. Reports are logged at the
|
||||||
|
* {@link LogLevel#DEBUG DEBUG} level unless there was a problem, in which case they are
|
||||||
|
* the {@link LogLevel#INFO INFO} level is used.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This initializer is not intended to be shared across multiple application context
|
||||||
|
* instances.
|
||||||
|
*
|
||||||
|
* @author Greg Turnquist
|
||||||
|
* @author Dave Syer
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
public class AutoConfigurationReportLoggingInitializer implements
|
||||||
|
ApplicationContextInitializer<ConfigurableApplicationContext>,
|
||||||
|
SpringApplicationErrorHandler {
|
||||||
|
|
||||||
|
private static final String LOGGER_BEAN = "autoConfigurationReportLogger";
|
||||||
|
|
||||||
|
private AutoConfigurationReportLogger loggerBean;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(ConfigurableApplicationContext applicationContext) {
|
||||||
|
this.loggerBean = new AutoConfigurationReportLogger(applicationContext);
|
||||||
|
applicationContext.getBeanFactory().registerSingleton(LOGGER_BEAN,
|
||||||
|
this.loggerBean);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleError(SpringApplication application,
|
||||||
|
ConfigurableApplicationContext applicationContext, String[] args,
|
||||||
|
Throwable exception) {
|
||||||
|
if (this.loggerBean != null) {
|
||||||
|
this.loggerBean.logAutoConfigurationReport(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring bean to actually perform the logging.
|
||||||
|
*/
|
||||||
|
public static class AutoConfigurationReportLogger implements
|
||||||
|
ApplicationListener<ContextRefreshedEvent> {
|
||||||
|
|
||||||
|
private final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
|
private final ConfigurableApplicationContext applicationContext;
|
||||||
|
|
||||||
|
private final AutoConfigurationReport report;
|
||||||
|
|
||||||
|
public AutoConfigurationReportLogger(
|
||||||
|
ConfigurableApplicationContext applicationContext) {
|
||||||
|
this.applicationContext = applicationContext;
|
||||||
|
// Get the report early in case the context fails to load
|
||||||
|
this.report = AutoConfigurationReport.get(this.applicationContext
|
||||||
|
.getBeanFactory());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||||
|
if (event.getApplicationContext() == this.applicationContext) {
|
||||||
|
logAutoConfigurationReport();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logAutoConfigurationReport() {
|
||||||
|
logAutoConfigurationReport(!this.applicationContext.isActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
void logAutoConfigurationReport(boolean isCrashReport) {
|
||||||
|
if (this.report.getConditionAndOutcomesBySource().size() > 0) {
|
||||||
|
if (isCrashReport && this.logger.isInfoEnabled()) {
|
||||||
|
this.logger.info(getLogMessage(this.report
|
||||||
|
.getConditionAndOutcomesBySource()));
|
||||||
|
}
|
||||||
|
else if (!isCrashReport && this.logger.isDebugEnabled()) {
|
||||||
|
this.logger.debug(getLogMessage(this.report
|
||||||
|
.getConditionAndOutcomesBySource()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private StringBuilder getLogMessage(Map<String, ConditionAndOutcomes> outcomes) {
|
||||||
|
StringBuilder message = new StringBuilder();
|
||||||
|
message.append("\n\n\n");
|
||||||
|
message.append("=========================\n");
|
||||||
|
message.append("AUTO-CONFIGURATION REPORT\n");
|
||||||
|
message.append("=========================\n\n\n");
|
||||||
|
message.append("Positive matches:\n");
|
||||||
|
message.append("-----------------\n");
|
||||||
|
for (Map.Entry<String, ConditionAndOutcomes> entry : outcomes.entrySet()) {
|
||||||
|
if (entry.getValue().isFullMatch()) {
|
||||||
|
addLogMessage(message, entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message.append("\n\n");
|
||||||
|
message.append("Negative matches:\n");
|
||||||
|
message.append("-----------------\n");
|
||||||
|
for (Map.Entry<String, ConditionAndOutcomes> entry : outcomes.entrySet()) {
|
||||||
|
if (!entry.getValue().isFullMatch()) {
|
||||||
|
addLogMessage(message, entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message.append("\n\n");
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addLogMessage(StringBuilder message, String source,
|
||||||
|
ConditionAndOutcomes conditionAndOutcomes) {
|
||||||
|
message.append("\n " + ClassUtils.getShortName(source) + "\n");
|
||||||
|
for (ConditionAndOutcome conditionAndOutcome : conditionAndOutcomes) {
|
||||||
|
message.append(" - ");
|
||||||
|
if (StringUtils.hasLength(conditionAndOutcome.getOutcome().getMessage())) {
|
||||||
|
message.append(conditionAndOutcome.getOutcome().getMessage());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
message.append(conditionAndOutcome.getOutcome().isMatch() ? "matched"
|
||||||
|
: "did not match");
|
||||||
|
}
|
||||||
|
message.append(" (");
|
||||||
|
message.append(ClassUtils.getShortName(conditionAndOutcome.getCondition()
|
||||||
|
.getClass()));
|
||||||
|
message.append(")\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ package org.springframework.boot.autoconfigure.condition;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.boot.autoconfigure.report.AutoConfigurationReport;
|
import org.springframework.boot.autoconfigure.AutoConfigurationReport;
|
||||||
import org.springframework.context.annotation.Condition;
|
import org.springframework.context.annotation.Condition;
|
||||||
import org.springframework.context.annotation.ConditionContext;
|
import org.springframework.context.annotation.ConditionContext;
|
||||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||||
|
|
@ -40,51 +40,48 @@ public abstract class SpringBootCondition implements Condition {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
||||||
ConditionOutcome result = getMatchOutcome(context, metadata);
|
String classOrMethodName = getClassOrMethodName(metadata);
|
||||||
StringBuilder message = getMessage(metadata, result);
|
ConditionOutcome outcome = getMatchOutcome(context, metadata);
|
||||||
|
logOutcome(classOrMethodName, outcome);
|
||||||
if (!result.isMatch()) {
|
recordInAutoConfigurationReport(context, classOrMethodName, outcome);
|
||||||
// Log non-matching conditions at debug
|
return outcome.isMatch();
|
||||||
if (this.logger.isDebugEnabled()) {
|
|
||||||
this.logger.debug(message);
|
|
||||||
}
|
|
||||||
AutoConfigurationReport.registerDecision(context, message.toString(), getClassOrMethodName(metadata), result);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log matching conditions at trace
|
private static String getClassOrMethodName(AnnotatedTypeMetadata metadata) {
|
||||||
if (this.logger.isTraceEnabled()) {
|
|
||||||
this.logger.trace(message);
|
|
||||||
}
|
|
||||||
AutoConfigurationReport.registerDecision(context, message.toString(), getClassOrMethodName(metadata), result);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private StringBuilder getMessage(AnnotatedTypeMetadata metadata, ConditionOutcome result) {
|
|
||||||
StringBuilder message = new StringBuilder();
|
|
||||||
message.append("Condition ");
|
|
||||||
message.append(ClassUtils.getShortName(getClass()));
|
|
||||||
message.append(" on ");
|
|
||||||
message.append(getClassOrMethodName(metadata));
|
|
||||||
message.append(result.isMatch() ? " matched" : " did not match");
|
|
||||||
if (StringUtils.hasLength(result.getMessage())) {
|
|
||||||
message.append(" due to ");
|
|
||||||
message.append(result.getMessage());
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getClassOrMethodName(AnnotatedTypeMetadata metadata) {
|
|
||||||
if (metadata instanceof ClassMetadata) {
|
if (metadata instanceof ClassMetadata) {
|
||||||
ClassMetadata classMetadata = (ClassMetadata) metadata;
|
ClassMetadata classMetadata = (ClassMetadata) metadata;
|
||||||
return classMetadata.getClassName();
|
return classMetadata.getClassName();
|
||||||
}
|
}
|
||||||
else if (metadata instanceof MethodMetadata) {
|
|
||||||
MethodMetadata methodMetadata = (MethodMetadata) metadata;
|
MethodMetadata methodMetadata = (MethodMetadata) metadata;
|
||||||
return methodMetadata.getDeclaringClassName() + "#" + methodMetadata.getMethodName();
|
return methodMetadata.getDeclaringClassName() + "#"
|
||||||
|
+ methodMetadata.getMethodName();
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
return "";
|
private void logOutcome(String classOrMethodName, ConditionOutcome outcome) {
|
||||||
|
if (this.logger.isTraceEnabled()) {
|
||||||
|
this.logger.trace(getLogMessage(classOrMethodName, outcome));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private StringBuilder getLogMessage(String classOrMethodName, ConditionOutcome outcome) {
|
||||||
|
StringBuilder message = new StringBuilder();
|
||||||
|
message.append("Condition ");
|
||||||
|
message.append(ClassUtils.getShortName(getClass()));
|
||||||
|
message.append(" on ");
|
||||||
|
message.append(classOrMethodName);
|
||||||
|
message.append(outcome.isMatch() ? " matched" : " did not match");
|
||||||
|
if (StringUtils.hasLength(outcome.getMessage())) {
|
||||||
|
message.append(" due to ");
|
||||||
|
message.append(outcome.getMessage());
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recordInAutoConfigurationReport(ConditionContext context,
|
||||||
|
String classOrMethodName, ConditionOutcome outcome) {
|
||||||
|
if (context.getBeanFactory() != null) {
|
||||||
|
AutoConfigurationReport.get(context.getBeanFactory())
|
||||||
|
.recordConditionEvaluation(classOrMethodName, this, outcome);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,6 +91,13 @@ public abstract class SpringBootCondition implements Condition {
|
||||||
public abstract ConditionOutcome getMatchOutcome(ConditionContext context,
|
public abstract ConditionOutcome getMatchOutcome(ConditionContext context,
|
||||||
AnnotatedTypeMetadata metadata);
|
AnnotatedTypeMetadata metadata);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if any of the specified conditions match.
|
||||||
|
* @param context the context
|
||||||
|
* @param metadata the annotation meta-data
|
||||||
|
* @param conditions conditions to test
|
||||||
|
* @return {@code true} if any condition matches.
|
||||||
|
*/
|
||||||
protected final boolean anyMatches(ConditionContext context,
|
protected final boolean anyMatches(ConditionContext context,
|
||||||
AnnotatedTypeMetadata metadata, Condition... conditions) {
|
AnnotatedTypeMetadata metadata, Condition... conditions) {
|
||||||
for (Condition condition : conditions) {
|
for (Condition condition : conditions) {
|
||||||
|
|
@ -104,6 +108,13 @@ public abstract class SpringBootCondition implements Condition {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if any of the specified condition matches.
|
||||||
|
* @param context the context
|
||||||
|
* @param metadata the annotation meta-data
|
||||||
|
* @param condition condition to test
|
||||||
|
* @return {@code true} if the condition matches.
|
||||||
|
*/
|
||||||
protected final boolean matches(ConditionContext context,
|
protected final boolean matches(ConditionContext context,
|
||||||
AnnotatedTypeMetadata metadata, Condition condition) {
|
AnnotatedTypeMetadata metadata, Condition condition) {
|
||||||
if (condition instanceof SpringBootCondition) {
|
if (condition instanceof SpringBootCondition) {
|
||||||
|
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2012-2013 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.report;
|
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collects details about decision made during autoconfiguration (pass or fail)
|
|
||||||
*
|
|
||||||
* @author Greg Turnquist
|
|
||||||
*/
|
|
||||||
public class AutoConfigurationDecision {
|
|
||||||
|
|
||||||
private final String message;
|
|
||||||
private final String classOrMethodName;
|
|
||||||
private final ConditionOutcome outcome;
|
|
||||||
|
|
||||||
public AutoConfigurationDecision(String message, String classOrMethodName,
|
|
||||||
ConditionOutcome outcome) {
|
|
||||||
this.message = message;
|
|
||||||
this.classOrMethodName = classOrMethodName;
|
|
||||||
this.outcome = outcome;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMessage() {
|
|
||||||
return this.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getClassOrMethodName() {
|
|
||||||
return this.classOrMethodName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConditionOutcome getOutcome() {
|
|
||||||
return this.outcome;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "AutoConfigurationDecision{" + "message='" + this.message + '\''
|
|
||||||
+ ", classOrMethodName='" + this.classOrMethodName + '\'' + ", outcome="
|
|
||||||
+ this.outcome + '}';
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o)
|
|
||||||
return true;
|
|
||||||
if (o == null || getClass() != o.getClass())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
AutoConfigurationDecision decision = (AutoConfigurationDecision) o;
|
|
||||||
|
|
||||||
if (this.message != null ? !this.message.equals(decision.message)
|
|
||||||
: decision.message != null)
|
|
||||||
return false;
|
|
||||||
if (this.outcome != null ? !this.outcome.equals(decision.outcome)
|
|
||||||
: decision.outcome != null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = this.message != null ? this.message.hashCode() : 0;
|
|
||||||
result = 31 * result + (this.outcome != null ? this.outcome.hashCode() : 0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,252 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2012-2013 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.report;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.springframework.beans.BeansException;
|
|
||||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.ApplicationContextAware;
|
|
||||||
import org.springframework.context.ApplicationListener;
|
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
|
||||||
import org.springframework.context.annotation.ConditionContext;
|
|
||||||
import org.springframework.context.event.ContextRefreshedEvent;
|
|
||||||
import org.springframework.util.ClassUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bean used to gather auto-configuration decisions, and then generate a collection of
|
|
||||||
* info for beans that were created as well as situations where the conditional outcome
|
|
||||||
* was negative.
|
|
||||||
*
|
|
||||||
* @author Greg Turnquist
|
|
||||||
* @author Dave Syer
|
|
||||||
*/
|
|
||||||
public class AutoConfigurationReport implements ApplicationContextAware,
|
|
||||||
ApplicationListener<ContextRefreshedEvent> {
|
|
||||||
|
|
||||||
private static final String AUTO_CONFIGURATION_REPORT = "autoConfigurationReport";
|
|
||||||
|
|
||||||
private static Log logger = LogFactory.getLog(AutoConfigurationReport.class);
|
|
||||||
|
|
||||||
private Set<CreatedBeanInfo> beansCreated = new LinkedHashSet<CreatedBeanInfo>();
|
|
||||||
|
|
||||||
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>> negative = new LinkedHashMap<String, List<String>>();
|
|
||||||
|
|
||||||
private ConfigurableApplicationContext context;
|
|
||||||
|
|
||||||
private boolean initialized = false;
|
|
||||||
|
|
||||||
public static void registerDecision(ConditionContext context, String message,
|
|
||||||
String classOrMethodName, ConditionOutcome outcome) {
|
|
||||||
if (context.getBeanFactory().containsBeanDefinition(AUTO_CONFIGURATION_REPORT)
|
|
||||||
|| context.getBeanFactory().containsSingleton(AUTO_CONFIGURATION_REPORT)) {
|
|
||||||
AutoConfigurationReport autoconfigurationReport = context.getBeanFactory()
|
|
||||||
.getBean(AUTO_CONFIGURATION_REPORT, AutoConfigurationReport.class);
|
|
||||||
autoconfigurationReport.registerDecision(message, classOrMethodName, outcome);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AutoConfigurationReport registerReport(
|
|
||||||
ConfigurableApplicationContext applicationContext,
|
|
||||||
ConfigurableListableBeanFactory beanFactory) {
|
|
||||||
if (!beanFactory.containsBean(AutoConfigurationReport.AUTO_CONFIGURATION_REPORT)) {
|
|
||||||
AutoConfigurationReport report = new AutoConfigurationReport();
|
|
||||||
report.setApplicationContext(applicationContext);
|
|
||||||
beanFactory.registerSingleton(
|
|
||||||
AutoConfigurationReport.AUTO_CONFIGURATION_REPORT, report);
|
|
||||||
}
|
|
||||||
return beanFactory.getBean(AutoConfigurationReport.AUTO_CONFIGURATION_REPORT,
|
|
||||||
AutoConfigurationReport.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void registerDecision(String message, String classOrMethodName,
|
|
||||||
ConditionOutcome outcome) {
|
|
||||||
AutoConfigurationDecision decision = new AutoConfigurationDecision(message,
|
|
||||||
classOrMethodName, outcome);
|
|
||||||
if (!this.autoconfigurationDecisions.containsKey(classOrMethodName)) {
|
|
||||||
this.autoconfigurationDecisions.put(classOrMethodName,
|
|
||||||
new ArrayList<AutoConfigurationDecision>());
|
|
||||||
}
|
|
||||||
this.autoconfigurationDecisions.get(classOrMethodName).add(decision);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<CreatedBeanInfo> getBeansCreated() {
|
|
||||||
return this.beansCreated;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, List<String>> getNegativeDecisions() {
|
|
||||||
return this.negative;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<Class<?>> getBeanTypesCreated() {
|
|
||||||
Set<Class<?>> beanTypesCreated = new HashSet<Class<?>>();
|
|
||||||
for (CreatedBeanInfo bootCreatedBeanInfo : this.getBeansCreated()) {
|
|
||||||
beanTypesCreated.add(bootCreatedBeanInfo.getType());
|
|
||||||
}
|
|
||||||
return beanTypesCreated;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getBeanNamesCreated() {
|
|
||||||
Set<String> beanNamesCreated = new HashSet<String>();
|
|
||||||
for (CreatedBeanInfo bootCreatedBeanInfo : this.getBeansCreated()) {
|
|
||||||
beanNamesCreated.add(bootCreatedBeanInfo.getName());
|
|
||||||
}
|
|
||||||
return beanNamesCreated;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setApplicationContext(ApplicationContext applicationContext)
|
|
||||||
throws BeansException {
|
|
||||||
this.context = (ConfigurableApplicationContext) applicationContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
|
||||||
initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initialize() {
|
|
||||||
if (!this.initialized) {
|
|
||||||
synchronized (this) {
|
|
||||||
if (!this.initialized) {
|
|
||||||
this.initialized = true;
|
|
||||||
try {
|
|
||||||
splitDecisionsIntoPositiveAndNegative();
|
|
||||||
scanPositiveDecisionsForBeansBootCreated();
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (shouldLogReport() && logger.isInfoEnabled()) {
|
|
||||||
logger.info("Created beans:");
|
|
||||||
for (CreatedBeanInfo info : this.beansCreated) {
|
|
||||||
logger.info(info);
|
|
||||||
}
|
|
||||||
logger.info("Negative decisions:");
|
|
||||||
for (String key : this.negative.keySet()) {
|
|
||||||
logger.info(key + ": " + this.negative.get(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean shouldLogReport() {
|
|
||||||
String debug = this.context.getEnvironment().getProperty("debug", "false")
|
|
||||||
.toLowerCase().trim();
|
|
||||||
return debug.equals("true") || debug.equals("") //
|
|
||||||
// inactive context is a sign that it crashed
|
|
||||||
|| !this.context.isActive();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
private synchronized void splitDecisionsIntoPositiveAndNegative() {
|
|
||||||
for (String key : this.autoconfigurationDecisions.keySet()) {
|
|
||||||
boolean match = true;
|
|
||||||
for (AutoConfigurationDecision decision : this.autoconfigurationDecisions
|
|
||||||
.get(key)) {
|
|
||||||
if (!decision.getOutcome().isMatch()) {
|
|
||||||
match = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (match) {
|
|
||||||
if (!this.positive.containsKey(key)) {
|
|
||||||
this.positive.put(key, new ArrayList<String>());
|
|
||||||
}
|
|
||||||
for (AutoConfigurationDecision decision : this.autoconfigurationDecisions
|
|
||||||
.get(key)) {
|
|
||||||
this.positive.get(key).add(decision.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (!this.negative.containsKey(key)) {
|
|
||||||
this.negative.put(key, new ArrayList<String>());
|
|
||||||
}
|
|
||||||
for (AutoConfigurationDecision decision : this.autoconfigurationDecisions
|
|
||||||
.get(key)) {
|
|
||||||
this.negative.get(key).add(decision.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scan all the decisions based on successful outcome, and try to find the
|
|
||||||
* corresponding beans Boot created.
|
|
||||||
*/
|
|
||||||
private synchronized void scanPositiveDecisionsForBeansBootCreated() {
|
|
||||||
for (String key : this.positive.keySet()) {
|
|
||||||
for (AutoConfigurationDecision decision : this.autoconfigurationDecisions
|
|
||||||
.get(key)) {
|
|
||||||
for (String beanName : this.context.getBeanDefinitionNames()) {
|
|
||||||
Object bean = null;
|
|
||||||
if (decision.getMessage().contains(beanName)
|
|
||||||
&& decision.getMessage().contains("matched")) {
|
|
||||||
try {
|
|
||||||
bean = this.context.getBean(beanName);
|
|
||||||
boolean anyMethodsAreBeans = false;
|
|
||||||
for (Method method : bean.getClass().getMethods()) {
|
|
||||||
if (this.context.containsBean(method.getName())) {
|
|
||||||
this.beansCreated.add(new CreatedBeanInfo(method
|
|
||||||
.getName(), method.getReturnType(),
|
|
||||||
this.positive.get(key)));
|
|
||||||
anyMethodsAreBeans = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!anyMethodsAreBeans) {
|
|
||||||
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)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2012-2013 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.report;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.SpringApplicationErrorHandler;
|
|
||||||
import org.springframework.context.ApplicationContextInitializer;
|
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Dave Syer
|
|
||||||
*/
|
|
||||||
public class AutoConfigurationReportApplicationContextInitializer implements
|
|
||||||
ApplicationContextInitializer<ConfigurableApplicationContext>,
|
|
||||||
SpringApplicationErrorHandler {
|
|
||||||
|
|
||||||
private AutoConfigurationReport report;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize(ConfigurableApplicationContext applicationContext) {
|
|
||||||
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
|
|
||||||
this.report = AutoConfigurationReport.registerReport(applicationContext,
|
|
||||||
beanFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleError(SpringApplication application,
|
|
||||||
ConfigurableApplicationContext applicationContext, String[] args,
|
|
||||||
Throwable exception) {
|
|
||||||
if (this.report != null) {
|
|
||||||
this.report.initialize(); // salvage a report if possible
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2012-2013 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.report;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A collection of data about a bean created by Boot
|
|
||||||
*
|
|
||||||
* @author Greg Turnquist
|
|
||||||
*/
|
|
||||||
public class BootCreatedBeanInfo {
|
|
||||||
|
|
||||||
private final String beanName;
|
|
||||||
private final Class<?> beanType;
|
|
||||||
private final List<String> decisions;
|
|
||||||
|
|
||||||
public BootCreatedBeanInfo(String beanName, Object bean, List<String> decisions) {
|
|
||||||
this.beanName = beanName;
|
|
||||||
this.beanType = bean.getClass();
|
|
||||||
this.decisions = decisions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BootCreatedBeanInfo(String beanName, Class<?> declaredBeanType, List<String> decisions) {
|
|
||||||
this.beanName = beanName;
|
|
||||||
this.beanType = declaredBeanType;
|
|
||||||
this.decisions = decisions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "BootCreatedBeanInfo{" + "beanName='" + beanName + '\'' + ", beanType=" + beanType
|
|
||||||
+ ", decisions=" + decisions + '}';
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBeanName() {
|
|
||||||
return beanName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Class<?> getBeanType() {
|
|
||||||
return beanType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getDecisions() {
|
|
||||||
return decisions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o)
|
|
||||||
return true;
|
|
||||||
if (o == null || getClass() != o.getClass())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
BootCreatedBeanInfo bootCreatedBeanInfo = (BootCreatedBeanInfo) o;
|
|
||||||
|
|
||||||
if (beanName != null ? !beanName.equals(bootCreatedBeanInfo.beanName)
|
|
||||||
: bootCreatedBeanInfo.beanName != null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return beanName != null ? beanName.hashCode() : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2012-2013 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.report;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A collection of data about a bean created by Boot
|
|
||||||
*
|
|
||||||
* @author Greg Turnquist
|
|
||||||
*/
|
|
||||||
public class CreatedBeanInfo {
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
private final Class<?> type;
|
|
||||||
private final List<String> decisions;
|
|
||||||
|
|
||||||
public CreatedBeanInfo(String beanName, Class<?> declaredBeanType,
|
|
||||||
List<String> decisions) {
|
|
||||||
this.name = beanName;
|
|
||||||
this.type = declaredBeanType;
|
|
||||||
this.decisions = decisions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "{" + "name='" + this.name + '\'' + ", type=" + this.type + ", decisions="
|
|
||||||
+ this.decisions + '}';
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return this.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Class<?> getType() {
|
|
||||||
return this.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getDecisions() {
|
|
||||||
return this.decisions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o)
|
|
||||||
return true;
|
|
||||||
if (o == null || getClass() != o.getClass())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
CreatedBeanInfo bootCreatedBeanInfo = (CreatedBeanInfo) o;
|
|
||||||
|
|
||||||
if (this.name != null ? !this.name.equals(bootCreatedBeanInfo.name)
|
|
||||||
: bootCreatedBeanInfo.name != null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return this.name != null ? this.name.hashCode() : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -20,5 +20,6 @@ org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
|
||||||
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
|
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
|
||||||
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
|
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
|
||||||
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration
|
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration
|
||||||
|
|
||||||
org.springframework.context.ApplicationContextInitializer=\
|
org.springframework.context.ApplicationContextInitializer=\
|
||||||
org.springframework.boot.autoconfigure.report.AutoConfigurationReportApplicationContextInitializer
|
org.springframework.boot.autoconfigure.AutoConfigurationReportLoggingInitializer
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2013 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.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogConfigurationException;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.commons.logging.impl.LogFactoryImpl;
|
||||||
|
import org.apache.commons.logging.impl.NoOpLog;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurationReportLoggingInitializer.AutoConfigurationReportLogger;
|
||||||
|
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
|
||||||
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.BDDMockito.willAnswer;
|
||||||
|
import static org.mockito.Matchers.anyObject;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link AutoConfigurationReportLoggingInitializer}.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
public class AutoConfigurationReportLoggingInitializerTests {
|
||||||
|
|
||||||
|
private static ThreadLocal<Log> logThreadLocal = new ThreadLocal<Log>();
|
||||||
|
|
||||||
|
private Log log;
|
||||||
|
|
||||||
|
private AutoConfigurationReportLoggingInitializer initializer;
|
||||||
|
|
||||||
|
protected List<String> debugLog = new ArrayList<String>();
|
||||||
|
|
||||||
|
protected List<String> infoLog = new ArrayList<String>();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
|
||||||
|
this.log = mock(Log.class);
|
||||||
|
logThreadLocal.set(this.log);
|
||||||
|
|
||||||
|
given(this.log.isDebugEnabled()).willReturn(true);
|
||||||
|
willAnswer(new Answer<Object>() {
|
||||||
|
@Override
|
||||||
|
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||||
|
return AutoConfigurationReportLoggingInitializerTests.this.debugLog
|
||||||
|
.add(String.valueOf(invocation.getArguments()[0]));
|
||||||
|
}
|
||||||
|
}).given(this.log).debug(anyObject());
|
||||||
|
|
||||||
|
given(this.log.isInfoEnabled()).willReturn(true);
|
||||||
|
willAnswer(new Answer<Object>() {
|
||||||
|
@Override
|
||||||
|
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||||
|
return AutoConfigurationReportLoggingInitializerTests.this.infoLog
|
||||||
|
.add(String.valueOf(invocation.getArguments()[0]));
|
||||||
|
}
|
||||||
|
}).given(this.log).info(anyObject());
|
||||||
|
|
||||||
|
LogFactory.releaseAll();
|
||||||
|
System.setProperty(LogFactory.FACTORY_PROPERTY, MockLogFactory.class.getName());
|
||||||
|
this.initializer = new AutoConfigurationReportLoggingInitializer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void cleanup() {
|
||||||
|
System.clearProperty(LogFactory.FACTORY_PROPERTIES);
|
||||||
|
LogFactory.releaseAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void logsDebugOnContextRefresh() {
|
||||||
|
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||||
|
this.initializer.initialize(context);
|
||||||
|
context.register(Config.class);
|
||||||
|
context.refresh();
|
||||||
|
assertThat(this.debugLog.size(), not(equalTo(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void logsInfoOnError() {
|
||||||
|
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||||
|
this.initializer.initialize(context);
|
||||||
|
context.register(ErrorConfig.class);
|
||||||
|
try {
|
||||||
|
context.refresh();
|
||||||
|
fail("Did not error");
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
this.initializer.handleError(null, context, new String[] {}, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(this.debugLog.size(), equalTo(0));
|
||||||
|
assertThat(this.infoLog.size(), not(equalTo(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void logsOutput() throws Exception {
|
||||||
|
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||||
|
this.initializer.initialize(context);
|
||||||
|
context.register(Config.class);
|
||||||
|
context.refresh();
|
||||||
|
for (String message : this.debugLog) {
|
||||||
|
System.out.println(message);
|
||||||
|
}
|
||||||
|
// Just basic sanity check, test is for visual inspection
|
||||||
|
String l = this.debugLog.get(0);
|
||||||
|
assertThat(l, containsString("not a web application (OnWebApplicationCondition)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MockLogFactory extends LogFactoryImpl {
|
||||||
|
@Override
|
||||||
|
public Log getInstance(String name) throws LogConfigurationException {
|
||||||
|
if (AutoConfigurationReportLogger.class.getName().equals(name)) {
|
||||||
|
return logThreadLocal.get();
|
||||||
|
}
|
||||||
|
return new NoOpLog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Import(WebMvcAutoConfiguration.class)
|
||||||
|
static class Config {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Import(WebMvcAutoConfiguration.class)
|
||||||
|
static class ErrorConfig {
|
||||||
|
@Bean
|
||||||
|
public String iBreak() {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -16,180 +16,134 @@
|
||||||
|
|
||||||
package org.springframework.boot.autoconfigure;
|
package org.springframework.boot.autoconfigure;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
import org.mockito.Mock;
|
||||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
import org.mockito.MockitoAnnotations;
|
||||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
import org.springframework.beans.factory.annotation.Configurable;
|
||||||
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||||
import org.springframework.boot.autoconfigure.jms.JmsTemplateAutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfigurationReport.ConditionAndOutcome;
|
||||||
import org.springframework.boot.autoconfigure.report.AutoConfigurationReport;
|
import org.springframework.boot.autoconfigure.AutoConfigurationReport.ConditionAndOutcomes;
|
||||||
import org.springframework.boot.autoconfigure.report.AutoConfigurationReportApplicationContextInitializer;
|
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||||
import org.springframework.boot.autoconfigure.report.CreatedBeanInfo;
|
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration;
|
|
||||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Condition;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
|
||||||
import org.springframework.jms.core.JmsTemplate;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
import static org.hamcrest.Matchers.sameInstance;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link AutoConfigurationReport}.
|
* Tests for {@link AutoConfigurationReport}.
|
||||||
*
|
*
|
||||||
* @author Greg Turnquist
|
* @author Greg Turnquist
|
||||||
|
* @author Phillip Webb
|
||||||
*/
|
*/
|
||||||
public class AutoConfigurationReportTests {
|
public class AutoConfigurationReportTests {
|
||||||
|
|
||||||
private AnnotationConfigApplicationContext context;
|
private DefaultListableBeanFactory beanFactory;
|
||||||
|
|
||||||
@Test
|
private AutoConfigurationReport report;
|
||||||
public void simpleReportTestCase() {
|
|
||||||
this.context = new AnnotationConfigApplicationContext();
|
|
||||||
this.context.register(TestConfiguration.class,
|
|
||||||
PropertyPlaceholderAutoConfiguration.class);
|
|
||||||
new AutoConfigurationReportApplicationContextInitializer()
|
|
||||||
.initialize(this.context);
|
|
||||||
this.context.refresh();
|
|
||||||
AutoConfigurationReport autoconfigSettings = this.context
|
|
||||||
.getBean(AutoConfigurationReport.class);
|
|
||||||
|
|
||||||
Set<CreatedBeanInfo> beansBootCreated = autoconfigSettings.getBeansCreated();
|
@Mock
|
||||||
Set<String> beanNamesBootCreated = autoconfigSettings.getBeanNamesCreated();
|
private Condition condition1;
|
||||||
Set<Class<?>> beanTypesBootCreated = autoconfigSettings.getBeanTypesCreated();
|
|
||||||
|
|
||||||
assertEquals(1, beansBootCreated.size());
|
@Mock
|
||||||
assertEquals(1, beanNamesBootCreated.size());
|
private Condition condition2;
|
||||||
assertEquals(1, beanTypesBootCreated.size());
|
|
||||||
|
|
||||||
assertTrue(beanNamesBootCreated.contains("propertySourcesPlaceholderConfigurer"));
|
@Mock
|
||||||
assertTrue(beanTypesBootCreated
|
private Condition condition3;
|
||||||
.contains(PropertySourcesPlaceholderConfigurer.class));
|
|
||||||
|
|
||||||
boolean foundPropertySourcesPlaceHolderConfigurer = false;
|
@Mock
|
||||||
int totalDecisions = 0;
|
private ConditionOutcome outcome1;
|
||||||
for (CreatedBeanInfo item : beansBootCreated) {
|
|
||||||
for (String decision : item.getDecisions()) {
|
@Mock
|
||||||
totalDecisions += 1;
|
private ConditionOutcome outcome2;
|
||||||
if (decision.contains("propertySourcesPlaceholderConfigurer matched")) {
|
|
||||||
foundPropertySourcesPlaceHolderConfigurer = true;
|
@Mock
|
||||||
}
|
private ConditionOutcome outcome3;
|
||||||
}
|
|
||||||
}
|
@Before
|
||||||
assertEquals(1, totalDecisions);
|
public void setup() {
|
||||||
assertTrue(foundPropertySourcesPlaceHolderConfigurer);
|
MockitoAnnotations.initMocks(this);
|
||||||
|
this.beanFactory = new DefaultListableBeanFactory();
|
||||||
|
this.report = AutoConfigurationReport.get(this.beanFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rabbitReportTest() {
|
public void get() throws Exception {
|
||||||
this.context = new AnnotationConfigApplicationContext();
|
assertThat(this.report, not(nullValue()));
|
||||||
this.context.register(TestConfiguration.class, RabbitAutoConfiguration.class,
|
assertThat(this.report,
|
||||||
AutoConfigurationReport.class);
|
sameInstance(AutoConfigurationReport.get(this.beanFactory)));
|
||||||
this.context.refresh();
|
|
||||||
AutoConfigurationReport autoconfigSettings = this.context
|
|
||||||
.getBean(AutoConfigurationReport.class);
|
|
||||||
|
|
||||||
Set<CreatedBeanInfo> beansBootCreated = autoconfigSettings.getBeansCreated();
|
|
||||||
Set<String> beanNamesBootCreated = autoconfigSettings.getBeanNamesCreated();
|
|
||||||
Set<Class<?>> beanTypesBootCreated = autoconfigSettings.getBeanTypesCreated();
|
|
||||||
|
|
||||||
assertEquals(3, beansBootCreated.size());
|
|
||||||
assertEquals(3, beanNamesBootCreated.size());
|
|
||||||
assertEquals(3, beanTypesBootCreated.size());
|
|
||||||
|
|
||||||
assertTrue(beanNamesBootCreated.contains("amqpAdmin"));
|
|
||||||
assertTrue(beanNamesBootCreated.contains("rabbitConnectionFactory"));
|
|
||||||
assertTrue(beanNamesBootCreated.contains("rabbitTemplate"));
|
|
||||||
|
|
||||||
assertTrue(beanTypesBootCreated.contains(RabbitAdmin.class));
|
|
||||||
assertTrue(beanTypesBootCreated.contains(ConnectionFactory.class));
|
|
||||||
assertTrue(beanTypesBootCreated.contains(RabbitTemplate.class));
|
|
||||||
|
|
||||||
boolean foundRabbitConnectionFactory = false;
|
|
||||||
boolean foundAmqpAdminExpressionCondition = false;
|
|
||||||
boolean foundAmqpAdminBeanCondition = false;
|
|
||||||
boolean foundRabbitTemplateCondition = false;
|
|
||||||
int totalDecisions = 0;
|
|
||||||
for (CreatedBeanInfo item : beansBootCreated) {
|
|
||||||
for (String decision : item.getDecisions()) {
|
|
||||||
totalDecisions += 1;
|
|
||||||
if (decision.contains("RabbitConnectionFactoryCreator matched")) {
|
|
||||||
foundRabbitConnectionFactory = true;
|
|
||||||
}
|
|
||||||
else if (decision.contains("OnExpressionCondition")
|
|
||||||
&& decision.contains("amqpAdmin matched due to SpEL expression")) {
|
|
||||||
foundAmqpAdminExpressionCondition = true;
|
|
||||||
}
|
|
||||||
else if (decision.contains("OnBeanCondition")
|
|
||||||
&& decision.contains("amqpAdmin matched")) {
|
|
||||||
foundAmqpAdminBeanCondition = true;
|
|
||||||
}
|
|
||||||
else if (decision.contains("rabbitTemplate matched")) {
|
|
||||||
foundRabbitTemplateCondition = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assertEquals(4, totalDecisions);
|
|
||||||
assertTrue(foundRabbitConnectionFactory);
|
|
||||||
assertTrue(foundAmqpAdminExpressionCondition);
|
|
||||||
assertTrue(foundAmqpAdminBeanCondition);
|
|
||||||
assertTrue(foundRabbitTemplateCondition);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void verifyItGathersNegativeMatches() {
|
public void recordConditionEvaluations() throws Exception {
|
||||||
this.context = new AnnotationConfigApplicationContext();
|
this.report.recordConditionEvaluation("a", this.condition1, this.outcome1);
|
||||||
this.context.register(TestConfiguration2.class,
|
this.report.recordConditionEvaluation("a", this.condition2, this.outcome2);
|
||||||
JmsTemplateAutoConfiguration.class, MultipartAutoConfiguration.class,
|
this.report.recordConditionEvaluation("b", this.condition3, this.outcome3);
|
||||||
AutoConfigurationReport.class);
|
|
||||||
this.context.refresh();
|
|
||||||
AutoConfigurationReport autoconfigSettings = this.context
|
|
||||||
.getBean(AutoConfigurationReport.class);
|
|
||||||
|
|
||||||
Map<String, List<String>> negatives = autoconfigSettings.getNegativeDecisions();
|
|
||||||
|
|
||||||
boolean foundMyOwnJmsTemplateAndBackedOff = false;
|
|
||||||
boolean didNotFindMultipartConfigElement = false;
|
|
||||||
int totalNegativeDecisions = 0;
|
|
||||||
for (String key : negatives.keySet()) {
|
|
||||||
for (String decision : negatives.get(key)) {
|
|
||||||
totalNegativeDecisions += 1;
|
|
||||||
if (decision
|
|
||||||
.contains("JmsTemplateAutoConfiguration#jmsTemplate did not match")
|
|
||||||
&& decision.contains("found the following [myOwnJmsTemplate]")) {
|
|
||||||
foundMyOwnJmsTemplateAndBackedOff = true;
|
|
||||||
}
|
|
||||||
else if (decision.contains("MultipartAutoConfiguration did not match")
|
|
||||||
&& decision
|
|
||||||
.contains("list['javax.servlet.MultipartConfigElement']")
|
|
||||||
&& decision.contains("found no beans")) {
|
|
||||||
didNotFindMultipartConfigElement = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// varying situations might cause multi-conditional beans to evaluate in different
|
|
||||||
// orders
|
|
||||||
assertTrue(totalNegativeDecisions >= 2);
|
|
||||||
assertTrue(foundMyOwnJmsTemplateAndBackedOff);
|
|
||||||
assertTrue(didNotFindMultipartConfigElement);
|
|
||||||
|
|
||||||
|
Map<String, ConditionAndOutcomes> map = this.report
|
||||||
|
.getConditionAndOutcomesBySource();
|
||||||
|
assertThat(map.size(), equalTo(2));
|
||||||
|
Iterator<ConditionAndOutcome> iterator = map.get("a").iterator();
|
||||||
|
ConditionAndOutcome conditionAndOutcome = iterator.next();
|
||||||
|
assertThat(conditionAndOutcome.getCondition(), equalTo(this.condition1));
|
||||||
|
assertThat(conditionAndOutcome.getOutcome(), equalTo(this.outcome1));
|
||||||
|
conditionAndOutcome = iterator.next();
|
||||||
|
assertThat(conditionAndOutcome.getCondition(), equalTo(this.condition2));
|
||||||
|
assertThat(conditionAndOutcome.getOutcome(), equalTo(this.outcome2));
|
||||||
|
assertThat(iterator.hasNext(), equalTo(false));
|
||||||
|
iterator = map.get("b").iterator();
|
||||||
|
conditionAndOutcome = iterator.next();
|
||||||
|
assertThat(conditionAndOutcome.getCondition(), equalTo(this.condition3));
|
||||||
|
assertThat(conditionAndOutcome.getOutcome(), equalTo(this.outcome3));
|
||||||
|
assertThat(iterator.hasNext(), equalTo(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Test
|
||||||
public static class TestConfiguration {
|
public void fullMatch() throws Exception {
|
||||||
|
prepareMatches(true, true, true);
|
||||||
|
assertThat(this.report.getConditionAndOutcomesBySource().get("a").isFullMatch(),
|
||||||
|
equalTo(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Test
|
||||||
public static class TestConfiguration2 {
|
public void notFullMatch() throws Exception {
|
||||||
@Bean
|
prepareMatches(true, false, true);
|
||||||
JmsTemplate myOwnJmsTemplate(javax.jms.ConnectionFactory connectionFactory) {
|
assertThat(this.report.getConditionAndOutcomesBySource().get("a").isFullMatch(),
|
||||||
return new JmsTemplate(connectionFactory);
|
equalTo(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void prepareMatches(boolean m1, boolean m2, boolean m3) {
|
||||||
|
given(this.outcome1.isMatch()).willReturn(m1);
|
||||||
|
given(this.outcome2.isMatch()).willReturn(m2);
|
||||||
|
given(this.outcome3.isMatch()).willReturn(m3);
|
||||||
|
this.report.recordConditionEvaluation("a", this.condition1, this.outcome1);
|
||||||
|
this.report.recordConditionEvaluation("a", this.condition2, this.outcome2);
|
||||||
|
this.report.recordConditionEvaluation("a", this.condition3, this.outcome3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("resource")
|
||||||
|
public void springBootConditionPopulatesReport() throws Exception {
|
||||||
|
AutoConfigurationReport report = AutoConfigurationReport
|
||||||
|
.get(new AnnotationConfigApplicationContext(Config.class)
|
||||||
|
.getBeanFactory());
|
||||||
|
assertThat(report.getConditionAndOutcomesBySource().size(), not(equalTo(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configurable
|
||||||
|
@Import(WebMvcAutoConfiguration.class)
|
||||||
|
static class Config {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue