Provide better diagnostics when an application fails to start

This commit introduces a new failure analysis mechanism that can be
used to provide diagnostics to a user when their application fails
to start.

When application context refresh fails. FailureAnalyzer
implementations are loaded via spring.factories. The analyzers are
called in order, with the first non-null analysis of the failure
being used. The analysis is reported to the use by
FailureAnalysisReporters which are also loaded via spring.factories.

A single FailureAnalysisReporter is provided out of the box. It logs
an error message with details of the analysis, providing the user
with a description of the failure and, if available, some actions
that they may be able to take to resolve the problem without also
displaying a stack trace that is of little value.

Two analysers are provided initially. One for an embedded servlet
container port clash and another for BeanCurrentlyInCreationExceptions.
More analysers are planned (for UnsatisfiedDependencyException and for
NoUniqueBeanDefinitionException) once some updates have been made
to Spring Framework to make those failures more amenable to analysis.

Closes gh-4907
pull/5052/merge
Andy Wilkinson 9 years ago
parent d454896755
commit df6c204193

@ -41,6 +41,9 @@ 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.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener;
@ -812,16 +815,13 @@ public class SpringApplication {
private void handleRunFailure(ConfigurableApplicationContext context,
SpringApplicationRunListeners listeners, Throwable exception) {
if (logger.isErrorEnabled()) {
logger.error("Application startup failed", exception);
registerLoggedException(exception);
}
try {
try {
handleExitCode(context, exception);
listeners.finished(context, exception);
}
finally {
reportFailure(exception);
if (context != null) {
context.close();
}
@ -833,6 +833,47 @@ public class SpringApplication {
ReflectionUtils.rethrowRuntimeException(exception);
}
private void reportFailure(Throwable failure) {
try {
FailureAnalysis failureAnalysis = analyzeFailure(failure);
if (failureAnalysis != null && reportFailureAnalysis(failureAnalysis)) {
registerLoggedException(failure);
return;
}
}
catch (Throwable ex) {
// Continue with normal handling of the original failure
}
if (logger.isErrorEnabled()) {
logger.error("Application startup failed", failure);
registerLoggedException(failure);
}
}
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.

@ -0,0 +1,49 @@
/*
* 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.context.embedded;
/**
* A {@code PortInUseException} is thrown when an embedded servlet container fails to
* start due to a port already being in use.
*
* @author Andy Wilkinson
* @since 1.4.0
*/
public class PortInUseException extends EmbeddedServletContainerException {
private final int port;
/**
* Creates a new port in use exception for the given {@code port}.
*
* @param port the port that was in use
*/
public PortInUseException(int port) {
super("Port " + port + " is already in use", null);
this.port = port;
}
/**
* Returns the port that was in use.
*
* @return the port
*/
public int getPort() {
return this.port;
}
}

@ -0,0 +1,47 @@
/*
* 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.context.embedded;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
/**
* A {@code FailureAnalyzer} that performs analysis of failures caused by a
* {@code PortInUseException}.
*
* @author Andy Wilkinson
* @since 1.4.0
*/
public class PortInUseFailureAnalyzer extends AbstractFailureAnalyzer {
@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;
}
}

@ -16,18 +16,21 @@
package org.springframework.boot.context.embedded.jetty;
import java.net.BindException;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.springframework.boot.context.embedded.EmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerException;
import org.springframework.boot.context.embedded.PortInUseException;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
@ -115,11 +118,23 @@ public class JettyEmbeddedServletContainer implements EmbeddedServletContainer {
}
Connector[] connectors = this.server.getConnectors();
for (Connector connector : connectors) {
connector.start();
try {
connector.start();
}
catch (BindException ex) {
if (connector instanceof NetworkConnector) {
throw new PortInUseException(
((NetworkConnector) connector).getPort());
}
throw ex;
}
}
JettyEmbeddedServletContainer.logger
.info("Jetty started on port(s) " + getActualPortsDescription());
}
catch (EmbeddedServletContainerException ex) {
throw ex;
}
catch (Exception ex) {
throw new EmbeddedServletContainerException(
"Unable to start embedded Jetty servlet container", ex);

@ -33,6 +33,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.boot.context.embedded.EmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerException;
import org.springframework.boot.context.embedded.PortInUseException;
import org.springframework.util.Assert;
/**
@ -166,27 +167,28 @@ public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer
if (connector != null && this.autoStart) {
startConnector(connector);
}
// Ensure process isn't left running if it actually failed to start
if (connectorsHaveFailedToStart()) {
stopSilently();
throw new IllegalStateException("Tomcat connector in failed state");
}
checkThatConnectorsHaveStarted();
TomcatEmbeddedServletContainer.logger
.info("Tomcat started on port(s): " + getPortsDescription(true));
}
catch (PortInUseException ex) {
stopSilently();
throw ex;
}
catch (Exception ex) {
throw new EmbeddedServletContainerException(
"Unable to start embedded Tomcat servlet container", ex);
}
}
private boolean connectorsHaveFailedToStart() {
private void checkThatConnectorsHaveStarted() {
for (Connector connector : this.tomcat.getService().findConnectors()) {
if (LifecycleState.FAILED.equals(connector.getState())) {
return true;
throw new PortInUseException(connector.getPort());
}
}
return false;
}
private void stopSilently() {

@ -17,6 +17,7 @@
package org.springframework.boot.context.embedded.undertow;
import java.lang.reflect.Field;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
@ -45,6 +46,7 @@ import org.xnio.channels.BoundChannel;
import org.springframework.boot.context.embedded.Compression;
import org.springframework.boot.context.embedded.EmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerException;
import org.springframework.boot.context.embedded.PortInUseException;
import org.springframework.http.HttpHeaders;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
@ -123,12 +125,32 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
UndertowEmbeddedServletContainer.logger
.info("Undertow started on port(s) " + getPortsDescription());
}
catch (ServletException ex) {
catch (Exception ex) {
if (findBindException(ex) != null) {
List<Port> failedPorts = getConfiguredPorts();
List<Port> actualPorts = getActualPorts();
failedPorts.removeAll(actualPorts);
if (failedPorts.size() == 1) {
throw new PortInUseException(
failedPorts.iterator().next().getNumber());
}
}
throw new EmbeddedServletContainerException(
"Unable to start embedded Undertow", ex);
}
}
private BindException findBindException(Exception ex) {
Throwable candidate = ex;
while (candidate != null) {
if (candidate instanceof BindException) {
return (BindException) candidate;
}
candidate = candidate.getCause();
}
return null;
}
private Undertow createUndertowServer() throws ServletException {
HttpHandler httpHandler = this.manager.start();
httpHandler = getContextHandler(httpHandler);
@ -175,14 +197,14 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
}
private String getPortsDescription() {
List<Port> ports = getPorts();
List<Port> ports = getActualPorts();
if (!ports.isEmpty()) {
return StringUtils.collectionToDelimitedString(ports, " ");
}
return "unknown";
}
private List<Port> getPorts() {
private List<Port> getActualPorts() {
List<Port> ports = new ArrayList<Port>();
try {
if (!this.autoStart) {
@ -218,6 +240,36 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
return null;
}
private List<Port> getConfiguredPorts() {
List<Port> ports = new ArrayList<Port>();
for (Object listener : extractListeners()) {
try {
ports.add(getPortFromListener(listener));
}
catch (Exception ex) {
// Continue
}
}
return ports;
}
@SuppressWarnings("unchecked")
private List<Object> extractListeners() {
Field listenersField = ReflectionUtils.findField(Undertow.class, "listeners");
ReflectionUtils.makeAccessible(listenersField);
return (List<Object>) ReflectionUtils.getField(listenersField, this.undertow);
}
private Port getPortFromListener(Object listener) {
Field typeField = ReflectionUtils.findField(listener.getClass(), "type");
ReflectionUtils.makeAccessible(typeField);
String protocol = ReflectionUtils.getField(typeField, listener).toString();
Field portField = ReflectionUtils.findField(listener.getClass(), "port");
ReflectionUtils.makeAccessible(portField);
int port = (Integer) ReflectionUtils.getField(portField, listener);
return new Port(port, protocol);
}
@Override
public synchronized void stop() throws EmbeddedServletContainerException {
if (this.started) {
@ -235,7 +287,7 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
@Override
public int getPort() {
List<Port> ports = getPorts();
List<Port> ports = getActualPorts();
if (ports.isEmpty()) {
return 0;
}
@ -243,7 +295,7 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
}
/**
* An active undertow port.
* An active Undertow port.
*/
private final static class Port {
@ -265,6 +317,29 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
return this.number + " (" + this.protocol + ")";
}
@Override
public int hashCode() {
return this.number;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Port other = (Port) obj;
if (this.number != other.number) {
return false;
}
return true;
}
}
private static class CompressibleMimeTypePredicate implements Predicate {

@ -0,0 +1,40 @@
/*
* 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;
/**
* Abstract base class for most {@code FailureAnalyzer} implementations.
*
* @author Andy Wilkinson
* @since 1.4.0
*/
public abstract class AbstractFailureAnalyzer implements FailureAnalyzer {
@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;
}
candidate = candidate.getCause();
}
return mostSpecificMatch;
}
}

@ -0,0 +1,87 @@
/*
* 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.ArrayList;
import java.util.List;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
import org.springframework.util.StringUtils;
/**
* A {@link FailureAnalyzer} the performs analysis of failures caused by a
* {@link BeanCurrentlyInCreationException}.
*
* @author Andy Wilkinson
* @since 1.4.0
*/
public class BeanCurrentlyInCreationFailureAnalyzer extends AbstractFailureAnalyzer {
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));
}
}
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);
}
return null;
}
private String getDescription(BeanCreationException ex) {
if (StringUtils.hasText(ex.getResourceDescription())) {
return String.format(" defined in %s", ex.getResourceDescription());
}
else if (causedByFieldAutowiringFailure(ex)) {
return String.format(" (field %s)",
ex.getCause().getMessage().substring(
FIELD_AUTOWIRING_FAILURE_MESSAGE_PREFIX.length(),
ex.getCause().getMessage().indexOf(";")));
}
return "";
}
private boolean causedByFieldAutowiringFailure(BeanCreationException ex) {
return ex.getCause() instanceof BeanCreationException && ex.getCause()
.getMessage().startsWith(FIELD_AUTOWIRING_FAILURE_MESSAGE_PREFIX);
}
}

@ -0,0 +1,75 @@
/*
* 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;
/**
* The result of analyzing a failure.
*
* @author Andy Wilkinson
* @since 1.4.0
*/
public class FailureAnalysis {
private final String description;
private final String action;
private final Throwable cause;
/**
* 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
*/
public FailureAnalysis(String description, String action, Throwable cause) {
this.description = description;
this.action = action;
this.cause = cause;
}
/**
* Returns a description of the failure.
*
* @return the description
*/
public String getDescription() {
return this.description;
}
/**
* Returns the action, if any, to be taken to address the failure.
*
* @return the action or {@code null}
*/
public String getAction() {
return this.action;
}
/**
* Returns the cause of the failure.
*
* @return the cause
*/
public Throwable getCause() {
return this.cause;
}
}

@ -0,0 +1,34 @@
/*
* 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;
/**
* Reports a {@code FailureAnalysis} to the user.
*
* @author Andy Wilkinson
* @since 1.4.0
*/
public interface FailureAnalysisReporter {
/**
* Reports the given {@code failureAnalysis} to the user.
*
* @param failureAnalysis the analysis
*/
void report(FailureAnalysis failureAnalysis);
}

@ -0,0 +1,37 @@
/*
* 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;
/**
* A {@code FailureAnalyzer} is used to analyze a failure and provide diagnostic
* information that can be displayed to the user.
*
* @author Andy Wilkinson
* @since 1.4.0
*/
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}
*/
FailureAnalysis analyze(Throwable failure);
}

@ -0,0 +1,61 @@
/*
* 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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StringUtils;
/**
* {@link FailureAnalysisReporter} that logs the failure analysis.
*
* @author Andy Wilkinson
* @since 1.4.0
*/
public final class LoggingFailureAnalysisReporter implements FailureAnalysisReporter {
private static final Log logger = LogFactory
.getLog(LoggingFailureAnalysisReporter.class);
@Override
public void report(FailureAnalysis failureAnalysis) {
if (logger.isDebugEnabled()) {
logger.debug("Application failed to start due to an exception",
failureAnalysis.getCause());
}
if (logger.isErrorEnabled()) {
logger.error(buildMessage(failureAnalysis));
}
}
private String buildMessage(FailureAnalysis failureAnalysis) {
StringBuilder builder = new StringBuilder();
builder.append(String.format("%n%n"));
builder.append(String.format("***************************%n"));
builder.append(String.format("APPLICATION FAILED TO START%n"));
builder.append(String.format("***************************%n%n"));
builder.append(String.format("Description:%n%n"));
builder.append(String.format("%s%n", failureAnalysis.getDescription()));
if (StringUtils.hasText(failureAnalysis.getAction())) {
builder.append(String.format("%nAction:%n%n"));
builder.append(String.format("%s%n", failureAnalysis.getAction()));
}
return builder.toString();
}
}

@ -31,9 +31,8 @@ import org.springframework.core.ResolvableType;
/**
* A {@link SmartApplicationListener} that reacts to {@link ApplicationStartedEvent start
* events} by logging the classpath of the thread context class loader (TCCL) at
* {@code DEBUG} level and to {@link ApplicationFailedEvent error events} by logging the
* TCCL's classpath at {@code INFO} level.
* events} and to {@link ApplicationFailedEvent failed events} by logging the classpath of
* the thread context class loader (TCCL) at {@code DEBUG} level.
*
* @author Andy Wilkinson
*/
@ -46,15 +45,13 @@ public final class ClasspathLoggingApplicationListener
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationStartedEvent) {
if (this.logger.isDebugEnabled()) {
if (this.logger.isDebugEnabled()) {
if (event instanceof ApplicationStartedEvent) {
this.logger
.debug("Application started with classpath: " + getClasspath());
}
}
else if (event instanceof ApplicationFailedEvent) {
if (this.logger.isInfoEnabled()) {
this.logger.info(
else if (event instanceof ApplicationFailedEvent) {
this.logger.debug(
"Application failed to start with classpath: " + getClasspath());
}
}

@ -30,3 +30,12 @@ org.springframework.boot.logging.LoggingApplicationListener
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.context.embedded.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.BeanCurrentlyInCreationFailureAnalyzer
# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter

@ -23,7 +23,9 @@ import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
@ -81,6 +83,7 @@ import org.springframework.util.StreamUtils;
import org.springframework.util.concurrent.ListenableFuture;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@ -715,6 +718,64 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
assertThat(response.getHeaders().getFirst("server")).isEqualTo("MyServer");
}
@Test
public void portClashOfPrimaryConnectorResultsInPortInUseException()
throws IOException {
AbstractEmbeddedServletContainerFactory factory = getFactory();
final int port = SocketUtils.findAvailableTcpPort(40000);
factory.setPort(port);
this.container = factory.getEmbeddedServletContainer();
doWithBlockedPort(port, new Runnable() {
@Override
public void run() {
try {
AbstractEmbeddedServletContainerFactoryTests.this.container.start();
fail();
}
catch (PortInUseException ex) {
assertThat(ex.getPort()).isEqualTo(port);
}
}
});
}
@Test
public void portClashOfSecondaryConnectorResultsInPortInUseException()
throws IOException {
AbstractEmbeddedServletContainerFactory factory = getFactory();
factory.setPort(SocketUtils.findAvailableTcpPort(40000));
final int port = SocketUtils.findAvailableTcpPort(40000);
addConnector(port, factory);
this.container = factory.getEmbeddedServletContainer();
doWithBlockedPort(port, new Runnable() {
@Override
public void run() {
try {
AbstractEmbeddedServletContainerFactoryTests.this.container.start();
fail();
}
catch (PortInUseException ex) {
assertThat(ex.getPort()).isEqualTo(port);
}
}
});
}
protected abstract void addConnector(int port,
AbstractEmbeddedServletContainerFactory factory);
private boolean doTestCompression(int contentSize, String[] mimeTypes,
String[] excludedUserAgents) throws Exception {
String testContent = setUpFactoryForCompression(contentSize, mimeTypes,
@ -878,6 +939,18 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
return bean;
}
protected final void doWithBlockedPort(final int port, Runnable action)
throws IOException {
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(port));
try {
action.run();
}
finally {
serverSocket.close();
}
}
private class TestGzipInputStreamFactory implements InputStreamFactory {
private final AtomicBoolean requested = new AtomicBoolean(false);

@ -136,6 +136,22 @@ public class JettyEmbeddedServletContainerFactoryTests
.containsExactly("ALPHA", "BRAVO", "CHARLIE");
}
@Override
protected void addConnector(final int port,
AbstractEmbeddedServletContainerFactory factory) {
((JettyEmbeddedServletContainerFactory) factory)
.addServerCustomizers(new JettyServerCustomizer() {
@Override
public void customize(Server server) {
ServerConnector connector = new ServerConnector(server);
connector.setPort(port);
server.addConnector(connector);
}
});
}
private void assertTimeout(JettyEmbeddedServletContainerFactory factory,
int expected) {
this.container = factory.getEmbeddedServletContainer();

@ -17,9 +17,6 @@
package org.springframework.boot.context.embedded.tomcat;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
@ -42,14 +39,13 @@ import org.junit.After;
import org.junit.Test;
import org.mockito.InOrder;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactoryTests;
import org.springframework.boot.context.embedded.EmbeddedServletContainerException;
import org.springframework.boot.context.embedded.Ssl;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.SocketUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
@ -264,61 +260,13 @@ public class TomcatEmbeddedServletContainerFactoryTests
assertThat(jsseProtocol.getCiphers()).isEqualTo("ALPHA,BRAVO,CHARLIE");
}
@Test
public void primaryConnectorPortClashThrowsIllegalStateException()
throws InterruptedException, IOException {
final int port = SocketUtils.findAvailableTcpPort(40000);
doWithBlockedPort(port, new Runnable() {
@Override
public void run() {
TomcatEmbeddedServletContainerFactory factory = getFactory();
factory.setPort(port);
try {
TomcatEmbeddedServletContainerFactoryTests.this.container = factory
.getEmbeddedServletContainer();
TomcatEmbeddedServletContainerFactoryTests.this.container.start();
fail();
}
catch (EmbeddedServletContainerException ex) {
// Ignore
}
}
});
}
@Test
public void additionalConnectorPortClashThrowsIllegalStateException()
throws InterruptedException, IOException {
final int port = SocketUtils.findAvailableTcpPort(40000);
doWithBlockedPort(port, new Runnable() {
@Override
public void run() {
TomcatEmbeddedServletContainerFactory factory = getFactory();
Connector connector = new Connector(
"org.apache.coyote.http11.Http11NioProtocol");
connector.setPort(port);
factory.addAdditionalTomcatConnectors(connector);
try {
TomcatEmbeddedServletContainerFactoryTests.this.container = factory
.getEmbeddedServletContainer();
TomcatEmbeddedServletContainerFactoryTests.this.container.start();
fail();
}
catch (EmbeddedServletContainerException ex) {
// Ignore
}
}
});
@Override
protected void addConnector(int port,
AbstractEmbeddedServletContainerFactory factory) {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setPort(port);
((TomcatEmbeddedServletContainerFactory) factory)
.addAdditionalTomcatConnectors(connector);
}
@Test
@ -403,15 +351,4 @@ public class TomcatEmbeddedServletContainerFactoryTests
return ((TomcatEmbeddedServletContainer) this.container).getTomcat();
}
private void doWithBlockedPort(final int port, Runnable action) throws IOException {
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(port));
try {
action.run();
}
finally {
serverSocket.close();
}
}
}

@ -188,6 +188,19 @@ public class UndertowEmbeddedServletContainerFactoryTests
assertThat(accessLogDirectory.listFiles()).contains(accessLog);
}
@Override
protected void addConnector(final int port,
AbstractEmbeddedServletContainerFactory factory) {
((UndertowEmbeddedServletContainerFactory) factory)
.addBuilderCustomizers(new UndertowBuilderCustomizer() {
@Override
public void customize(Builder builder) {
builder.addHttpListener(port, "0.0.0.0");
}
});
}
@Override
protected Object getJspServlet() {
return null; // Undertow does not support JSPs

@ -0,0 +1,152 @@
/*
* 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 org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
/**
* Tests for {@link BeanCurrentlyInCreationFailureAnalyzer}
*
* @author Andy Wilkinson
*/
public class BeanCurrentlyInCreationFailureAnalyzerTests {
private final FailureAnalyzer analyzer = new BeanCurrentlyInCreationFailureAnalyzer();
@Test
public void cyclicBeanMethods() {
FailureAnalysis analysis = performAnalysis(CyclicBeanMethodsConfiguration.class);
assertThat(analysis.getDescription()).startsWith(
"There is a circular dependency between 3 beans in the application context:");
assertThat(analysis.getDescription()).contains(
"one defined in " + CyclicBeanMethodsConfiguration.class.getName());
assertThat(analysis.getDescription()).contains(
"two defined in " + CyclicBeanMethodsConfiguration.class.getName());
assertThat(analysis.getDescription()).contains(
"three defined in " + CyclicBeanMethodsConfiguration.class.getName());
}
@Test
public void cycleWithAutowiredFields() {
FailureAnalysis analysis = performAnalysis(CycleWithAutowiredFields.class);
assertThat(analysis.getDescription()).startsWith(
"There is a circular dependency between 3 beans in the application context:");
assertThat(analysis.getDescription()).contains("three defined in "
+ CycleWithAutowiredFields.BeanThreeConfiguration.class);
assertThat(analysis.getDescription())
.contains("one defined in " + CycleWithAutowiredFields.class.getName());
assertThat(analysis.getDescription())
.contains(CycleWithAutowiredFields.BeanTwoConfiguration.class.getName()
+ " (field private " + BeanThree.class.getName());
}
private FailureAnalysis performAnalysis(Class<?> configuration) {
FailureAnalysis analysis = this.analyzer.analyze(createFailure(configuration));
assertThat(analysis).isNotNull();
return analysis;
}
private Exception createFailure(Class<?> configuration) {
ConfigurableApplicationContext context = null;
try {
context = new AnnotationConfigApplicationContext(configuration);
}
catch (Exception ex) {
ex.printStackTrace();
return ex;
}
finally {
if (context != null) {
context.close();
}
}
fail("Expected failure did not occur");
return null;
}
@Configuration
static class CyclicBeanMethodsConfiguration {
@Bean
public BeanOne one(BeanTwo two) {
return new BeanOne();
}
@Bean
public BeanTwo two(BeanThree three) {
return new BeanTwo();
}
@Bean
public BeanThree three(BeanOne one) {
return new BeanThree();
}
}
@Configuration
public static class CycleWithAutowiredFields {
@Bean
public BeanOne one(BeanTwo two) {
return new BeanOne();
}
public static class BeanTwoConfiguration {
@SuppressWarnings("unused")
@Autowired
private BeanThree three;
@Bean
public BeanTwo two() {
return new BeanTwo();
}
}
public static class BeanThreeConfiguration {
@Bean
public BeanThree three(BeanOne one) {
return new BeanThree();
}
}
}
static class BeanOne {
}
static class BeanTwo {
}
static class BeanThree {
}
}
Loading…
Cancel
Save