From 2ca92e2a4578763c682866bdca015be52762d355 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 23 May 2017 10:46:34 +0200 Subject: [PATCH] Add failure analyzer for BeanCreationException Closes gh-9220 --- .../analyzer/BeanCreationFailureAnalyzer.java | 58 +++++++++++++ ...eanCurrentlyInCreationFailureAnalyzer.java | 2 + .../main/resources/META-INF/spring.factories | 1 + .../boot/SpringApplicationTests.java | 29 ------- .../BeanCreationFailureAnalyzerTest.java | 84 +++++++++++++++++++ 5 files changed, 145 insertions(+), 29 deletions(-) create mode 100644 spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BeanCreationFailureAnalyzer.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/BeanCreationFailureAnalyzerTest.java diff --git a/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BeanCreationFailureAnalyzer.java b/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BeanCreationFailureAnalyzer.java new file mode 100644 index 0000000000..d3c24db321 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BeanCreationFailureAnalyzer.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.diagnostics.analyzer; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; +import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.boot.diagnostics.FailureAnalyzer; +import org.springframework.core.annotation.Order; + +/** + * A {@link FailureAnalyzer} that performs analysis of failures caused by a + * {@link BeanCreationException}. + * + * @author Stephane Nicoll + * @see BeanCurrentlyInCreationFailureAnalyzer + */ +@Order(100) +public class BeanCreationFailureAnalyzer + extends AbstractFailureAnalyzer { + + @Override + protected FailureAnalysis analyze(Throwable rootFailure, + BeanCreationException cause) { + StringBuilder sb = new StringBuilder(); + sb.append("A bean named '").append(cause.getBeanName()).append("'"); + if (cause.getResourceDescription() != null) { + sb.append(" defined in ").append(cause.getResourceDescription()); + } + sb.append(" failed to be created:"); + sb.append(String.format("%n%n")); + Throwable nested = findMostNestedCause(cause); + sb.append(String.format("\t%s", nested.getMessage())); + return new FailureAnalysis(sb.toString(), null, cause); + } + + private Throwable findMostNestedCause(Throwable exception) { + if (exception.getCause() == null) { + return exception; + } + return findMostNestedCause(exception.getCause()); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BeanCurrentlyInCreationFailureAnalyzer.java b/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BeanCurrentlyInCreationFailureAnalyzer.java index 47d803ccd2..63f10e7ee3 100644 --- a/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BeanCurrentlyInCreationFailureAnalyzer.java +++ b/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BeanCurrentlyInCreationFailureAnalyzer.java @@ -25,6 +25,7 @@ import org.springframework.beans.factory.InjectionPoint; import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.core.annotation.Order; import org.springframework.util.StringUtils; /** @@ -33,6 +34,7 @@ import org.springframework.util.StringUtils; * * @author Andy Wilkinson */ +@Order(0) class BeanCurrentlyInCreationFailureAnalyzer extends AbstractFailureAnalyzer { diff --git a/spring-boot/src/main/resources/META-INF/spring.factories b/spring-boot/src/main/resources/META-INF/spring.factories index 2563865d9b..55bdda8653 100644 --- a/spring-boot/src/main/resources/META-INF/spring.factories +++ b/spring-boot/src/main/resources/META-INF/spring.factories @@ -33,6 +33,7 @@ org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor # Failure Analyzers org.springframework.boot.diagnostics.FailureAnalyzer=\ +org.springframework.boot.diagnostics.analyzer.BeanCreationFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\ diff --git a/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java b/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java index 4d9d0a4d6b..29d4c1f036 100644 --- a/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java @@ -805,25 +805,6 @@ public class SpringApplicationTests { TestPropertySourceUtils.INLINED_PROPERTIES_PROPERTY_SOURCE_NAME); } - @Test - public void failureResultsInSingleStackTrace() throws Exception { - ThreadGroup group = new ThreadGroup("main"); - Thread thread = new Thread(group, "main") { - @Override - public void run() { - SpringApplication application = new SpringApplication( - FailingConfig.class); - application.setWebEnvironment(false); - application.run(); - }; - }; - thread.start(); - thread.join(6000); - int occurrences = StringUtils.countOccurrencesOf(this.output.toString(), - "Caused by: java.lang.RuntimeException: ExpectedError"); - assertThat(occurrences).as("Expected single stacktrace").isEqualTo(1); - } - private Condition matchingPropertySource( final Class propertySourceClass, final String name) { return new Condition("has property source") { @@ -966,16 +947,6 @@ public class SpringApplicationTests { } - @Configuration - static class FailingConfig { - - @Bean - public Object fail() { - throw new RuntimeException("ExpectedError"); - } - - } - @Configuration static class CommandLineRunConfig { diff --git a/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/BeanCreationFailureAnalyzerTest.java b/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/BeanCreationFailureAnalyzerTest.java new file mode 100644 index 0000000000..4a0bfd312e --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/BeanCreationFailureAnalyzerTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.diagnostics.analyzer; + +import org.junit.Test; + +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; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +/** + * Tests for {@link BeanCreationFailureAnalyzer}. + * + * @author Stephane Nicoll + */ +public class BeanCreationFailureAnalyzerTest { + + private final FailureAnalyzer analyzer = new BeanCreationFailureAnalyzer(); + + @Test + public void failureWithDefinition() { + FailureAnalysis analysis = performAnalysis( + BeanCreationFailureConfiguration.class); + assertThat(analysis.getDescription()).contains("bean named 'foo'", + "Property bar is not set", + "defined in " + BeanCreationFailureConfiguration.class.getName()); + assertThat(analysis.getDescription()).doesNotContain("Failed to instantiate"); + assertThat(analysis.getAction()).isNull(); + } + + 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) { + return ex; + } + finally { + if (context != null) { + context.close(); + } + } + fail("Expected failure did not occur"); + return null; + } + + @Configuration + static class BeanCreationFailureConfiguration { + + @Bean + public String foo() { + throw new IllegalStateException("Property bar is not set"); + } + + } + +}