Polish diagnostics support

Rework a few parts of the diagnostics support:

- Move code from SpringApplication to FailureAnalyzers
- Allow AbstractFailureAnalyzer to take generic cause type
- Move own analyzers into a new package and make package private

See gh-4907
pull/5052/merge
Phillip Webb 9 years ago
parent c6c2959aa3
commit 03dad33b0c

@ -41,9 +41,7 @@ import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.diagnostics.FailureAnalysisReporter;
import org.springframework.boot.diagnostics.FailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalyzers;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener;
@ -835,8 +833,7 @@ public class SpringApplication {
private void reportFailure(Throwable failure) {
try {
FailureAnalysis failureAnalysis = analyzeFailure(failure);
if (failureAnalysis != null && reportFailureAnalysis(failureAnalysis)) {
if (FailureAnalyzers.analyzeAndReport(failure, getClass().getClassLoader())) {
registerLoggedException(failure);
return;
}
@ -850,30 +847,6 @@ public class SpringApplication {
}
}
private FailureAnalysis analyzeFailure(Throwable failure) {
List<FailureAnalyzer> analyzers = SpringFactoriesLoader
.loadFactories(FailureAnalyzer.class, getClass().getClassLoader());
for (FailureAnalyzer analyzer : analyzers) {
FailureAnalysis analysis = analyzer.analyze(failure);
if (analysis != null) {
return analysis;
}
}
return null;
}
private boolean reportFailureAnalysis(FailureAnalysis failureAnalysis) {
List<FailureAnalysisReporter> reporters = SpringFactoriesLoader.loadFactories(
FailureAnalysisReporter.class, getClass().getClassLoader());
if (!reporters.isEmpty()) {
for (FailureAnalysisReporter reporter : reporters) {
reporter.report(failureAnalysis);
}
return true;
}
return false;
}
/**
* Register that the given exception has been logged. By default, if the running in
* the main thread, this method will suppress additional printing of the stacktrace.

@ -29,7 +29,6 @@ public class PortInUseException extends EmbeddedServletContainerException {
/**
* Creates a new port in use exception for the given {@code port}.
*
* @param port the port that was in use
*/
public PortInUseException(int port) {
@ -39,7 +38,6 @@ public class PortInUseException extends EmbeddedServletContainerException {
/**
* Returns the port that was in use.
*
* @return the port
*/
public int getPort() {

@ -167,9 +167,7 @@ public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer
if (connector != null && this.autoStart) {
startConnector(connector);
}
checkThatConnectorsHaveStarted();
TomcatEmbeddedServletContainer.logger
.info("Tomcat started on port(s): " + getPortsDescription(true));
}

@ -16,25 +16,57 @@
package org.springframework.boot.diagnostics;
import org.springframework.core.ResolvableType;
/**
* Abstract base class for most {@code FailureAnalyzer} implementations.
*
* @param <T> The type of exception to analyze
* @author Andy Wilkinson
* @author Phillip Webb
* @since 1.4.0
*/
public abstract class AbstractFailureAnalyzer implements FailureAnalyzer {
public abstract class AbstractFailureAnalyzer<T extends Throwable>
implements FailureAnalyzer {
@Override
public FailureAnalysis analyze(Throwable failure) {
T cause = findCause(failure, getCauseType());
if (cause != null) {
return analyze(failure, cause);
}
return null;
}
/**
* Returns an analysis of the given {@code failure}, or {@code null} if no analysis
* was possible.
* @param rootFailure the root failure passed to the analyzer
* @param cause the actual found cause
* @return the analysis or {@code null}
*/
protected abstract FailureAnalysis analyze(Throwable rootFailure, T cause);
/**
* Return the cause type being handled by the analyzer. By default the class generic
* is used.
* @return the cause type
*/
@SuppressWarnings("unchecked")
protected Class<? extends T> getCauseType() {
return (Class<? extends T>) ResolvableType
.forClass(AbstractFailureAnalyzer.class, getClass()).resolveGeneric();
}
@SuppressWarnings("unchecked")
protected <T extends Throwable> T findFailure(Throwable failure, Class<T> type) {
Throwable candidate = failure;
T mostSpecificMatch = null;
while (candidate != null) {
if (type.isInstance(candidate)) {
mostSpecificMatch = (T) candidate;
protected final <E extends Throwable> T findCause(Throwable failure, Class<E> type) {
while (failure != null) {
if (type.isInstance(failure)) {
return (T) failure;
}
candidate = candidate.getCause();
failure = failure.getCause();
}
return mostSpecificMatch;
return null;
}
}

@ -34,7 +34,6 @@ public class FailureAnalysis {
* Creates a new {@code FailureAnalysis} with the given {@code description} and
* {@code action}, if any, that the user should take to address the problem. The
* failure had the given underlying {@code cause}.
*
* @param description the description
* @param action the action
* @param cause the cause
@ -47,7 +46,6 @@ public class FailureAnalysis {
/**
* Returns a description of the failure.
*
* @return the description
*/
public String getDescription() {
@ -56,7 +54,6 @@ public class FailureAnalysis {
/**
* Returns the action, if any, to be taken to address the failure.
*
* @return the action or {@code null}
*/
public String getAction() {
@ -65,7 +62,6 @@ public class FailureAnalysis {
/**
* Returns the cause of the failure.
*
* @return the cause
*/
public Throwable getCause() {

@ -26,9 +26,8 @@ public interface FailureAnalysisReporter {
/**
* Reports the given {@code failureAnalysis} to the user.
*
* @param failureAnalysis the analysis
* @param analysis the analysis
*/
void report(FailureAnalysis failureAnalysis);
void report(FailureAnalysis analysis);
}

@ -28,7 +28,6 @@ public interface FailureAnalyzer {
/**
* Returns an analysis of the given {@code failure}, or {@code null} if no analysis
* was possible.
*
* @param failure the failure
* @return the analysis or {@code null}
*/

@ -0,0 +1,67 @@
/*
* Copyright 2012-2016 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.diagnostics;
import java.util.List;
import org.springframework.core.io.support.SpringFactoriesLoader;
/**
* Utility to trigger {@link FailureAnalyzer} and {@link FailureAnalysisReporter}
* instances loaded from {@code spring.factories}.
*
* @author Andy Wilkinson
* @author Phillip Webb
* @since 1.4.0
*/
public final class FailureAnalyzers {
private FailureAnalyzers() {
}
public static boolean analyzeAndReport(Throwable failure, ClassLoader classLoader) {
List<FailureAnalyzer> analyzers = SpringFactoriesLoader
.loadFactories(FailureAnalyzer.class, classLoader);
List<FailureAnalysisReporter> reporters = SpringFactoriesLoader
.loadFactories(FailureAnalysisReporter.class, classLoader);
FailureAnalysis analysis = analyze(failure, analyzers);
return report(analysis, reporters);
}
private static FailureAnalysis analyze(Throwable failure,
List<FailureAnalyzer> analyzers) {
for (FailureAnalyzer analyzer : analyzers) {
FailureAnalysis analysis = analyzer.analyze(failure);
if (analysis != null) {
return analysis;
}
}
return null;
}
private static boolean report(FailureAnalysis analysis,
List<FailureAnalysisReporter> reporters) {
if (analysis == null || reporters.isEmpty()) {
return false;
}
for (FailureAnalysisReporter reporter : reporters) {
reporter.report(analysis);
}
return true;
}
}

@ -14,62 +14,59 @@
* limitations under the License.
*/
package org.springframework.boot.diagnostics;
package org.springframework.boot.diagnostics.analyzer;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.util.StringUtils;
/**
* A {@link FailureAnalyzer} the performs analysis of failures caused by a
* A {@link AbstractFailureAnalyzer} the performs analysis of failures caused by a
* {@link BeanCurrentlyInCreationException}.
*
* @author Andy Wilkinson
* @since 1.4.0
*/
public class BeanCurrentlyInCreationFailureAnalyzer extends AbstractFailureAnalyzer {
class BeanCurrentlyInCreationFailureAnalyzer
extends AbstractFailureAnalyzer<BeanCurrentlyInCreationException> {
private static final String FIELD_AUTOWIRING_FAILURE_MESSAGE_PREFIX = "Could not autowire field: ";
@Override
public FailureAnalysis analyze(Throwable failure) {
BeanCurrentlyInCreationException inCreationEx = findFailure(failure,
BeanCurrentlyInCreationException.class);
if (inCreationEx != null) {
List<String> beansInCycle = new ArrayList<String>();
Throwable candidate = failure;
while (candidate != null) {
if (candidate instanceof BeanCreationException) {
BeanCreationException creationEx = (BeanCreationException) candidate;
if (StringUtils.hasText(creationEx.getBeanName())) {
beansInCycle.add(
creationEx.getBeanName() + getDescription(creationEx));
}
protected FailureAnalysis analyze(Throwable rootFailure,
BeanCurrentlyInCreationException cause) {
List<String> beansInCycle = new ArrayList<String>();
Throwable candidate = rootFailure;
while (candidate != null) {
if (candidate instanceof BeanCreationException) {
BeanCreationException creationEx = (BeanCreationException) candidate;
if (StringUtils.hasText(creationEx.getBeanName())) {
beansInCycle
.add(creationEx.getBeanName() + getDescription(creationEx));
}
candidate = candidate.getCause();
}
StringBuilder message = new StringBuilder();
int uniqueBeans = beansInCycle.size() - 1;
message.append(String
.format("There is a circular dependency between %s beans in the "
+ "application context:%n", uniqueBeans));
for (String bean : beansInCycle) {
message.append(String.format("\t- %s%n", bean));
}
return new FailureAnalysis(message.toString(), null, failure);
candidate = candidate.getCause();
}
StringBuilder message = new StringBuilder();
int uniqueBeans = beansInCycle.size() - 1;
message.append(
String.format("There is a circular dependency between %s beans in the "
+ "application context:%n", uniqueBeans));
for (String bean : beansInCycle) {
message.append(String.format("\t- %s%n", bean));
}
return null;
return new FailureAnalysis(message.toString(), null, cause);
}
private String getDescription(BeanCreationException ex) {
if (StringUtils.hasText(ex.getResourceDescription())) {
return String.format(" defined in %s", ex.getResourceDescription());
}
else if (causedByFieldAutowiringFailure(ex)) {
if (causedByFieldAutowiringFailure(ex)) {
return String.format(" (field %s)",
ex.getCause().getMessage().substring(
FIELD_AUTOWIRING_FAILURE_MESSAGE_PREFIX.length(),

@ -14,8 +14,9 @@
* limitations under the License.
*/
package org.springframework.boot.context.embedded;
package org.springframework.boot.diagnostics.analyzer;
import org.springframework.boot.context.embedded.PortInUseException;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
@ -24,24 +25,18 @@ import org.springframework.boot.diagnostics.FailureAnalysis;
* {@code PortInUseException}.
*
* @author Andy Wilkinson
* @since 1.4.0
*/
public class PortInUseFailureAnalyzer extends AbstractFailureAnalyzer {
class PortInUseFailureAnalyzer extends AbstractFailureAnalyzer<PortInUseException> {
@Override
public FailureAnalysis analyze(Throwable failure) {
PortInUseException portInUseException = findFailure(failure,
PortInUseException.class);
if (portInUseException != null) {
return new FailureAnalysis(
"Embedded servlet container failed to start. Port "
+ portInUseException.getPort() + " was already in use.",
"Identify and stop the process that's listening on port "
+ portInUseException.getPort() + " or configure this "
+ "application to listen on another port.",
portInUseException);
}
return null;
protected FailureAnalysis analyze(Throwable rootFailure, PortInUseException cause) {
return new FailureAnalysis(
"Embedded servlet container failed to start. Port " + cause.getPort()
+ " was already in use.",
"Identify and stop the process that's listening on port "
+ cause.getPort() + " or configure this "
+ "application to listen on another port.",
cause);
}
}

@ -0,0 +1,20 @@
/*
* Copyright 2012-2014 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.
*/
/**
* Internal {@link org.springframework.boot.diagnostics.FailureAnalyzer} implementations.
*/
package org.springframework.boot.diagnostics.analyzer;

@ -0,0 +1,23 @@
/*
* Copyright 2012-2014 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.
*/
/**
* Support for failure analysis and reporting.
*
* @see org.springframework.boot.diagnostics.FailureAnalyzer
* @see org.springframework.boot.diagnostics.FailureAnalysisReporter
*/
package org.springframework.boot.diagnostics;

@ -33,8 +33,8 @@ org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.context.embedded.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.BeanCurrentlyInCreationFailureAnalyzer
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer
# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\

@ -14,11 +14,13 @@
* limitations under the License.
*/
package org.springframework.boot.diagnostics;
package org.springframework.boot.diagnostics.analyzer;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.diagnostics.FailureAnalyzer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
Loading…
Cancel
Save