Improve failure analysis action when circular references are allowed

Closes gh-27735
This commit is contained in:
Andy Wilkinson 2021-10-18 16:02:13 +01:00
parent 42ef97b9ec
commit 31d88c3d3c
2 changed files with 54 additions and 13 deletions

View File

@ -19,10 +19,14 @@ package org.springframework.boot.diagnostics.analyzer;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.util.StringUtils;
@ -33,7 +37,17 @@ import org.springframework.util.StringUtils;
*
* @author Andy Wilkinson
*/
class BeanCurrentlyInCreationFailureAnalyzer extends AbstractFailureAnalyzer<BeanCurrentlyInCreationException> {
class BeanCurrentlyInCreationFailureAnalyzer extends AbstractFailureAnalyzer<BeanCurrentlyInCreationException>
implements BeanFactoryAware {
private AbstractAutowireCapableBeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
this.beanFactory = (AbstractAutowireCapableBeanFactory) beanFactory;
}
}
@Override
protected FailureAnalysis analyze(Throwable rootFailure, BeanCurrentlyInCreationException cause) {
@ -41,12 +55,18 @@ class BeanCurrentlyInCreationFailureAnalyzer extends AbstractFailureAnalyzer<Bea
if (dependencyCycle == null) {
return null;
}
return new FailureAnalysis(buildMessage(dependencyCycle),
"Relying upon circular references is discouraged and they are prohibited by default. "
+ "Update your application to remove the dependency cycle between beans. "
+ "As a last resort, it may be possible to break the cycle automatically by setting "
+ "spring.main.allow-circular-references to true if you have not already done so.",
cause);
return new FailureAnalysis(buildMessage(dependencyCycle), action(), cause);
}
private String action() {
if (this.beanFactory != null && this.beanFactory.isAllowCircularReferences()) {
return "Despite circular references being allowed, the dependency cycle between beans could not be "
+ "broken. Update your application to remove the dependency cycle.";
}
return "Relying upon circular references is discouraged and they are prohibited by default. "
+ "Update your application to remove the dependency cycle between beans. "
+ "As a last resort, it may be possible to break the cycle automatically by setting "
+ "spring.main.allow-circular-references to true.";
}
private DependencyCycle findCycle(Throwable rootFailure) {

View File

@ -26,13 +26,12 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.diagnostics.FailureAnalyzer;
import org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzerTests.CycleWithAutowiredFields.BeanThreeConfiguration;
import org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzerTests.CycleWithAutowiredFields.BeanTwoConfiguration;
import org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzerTests.CyclicBeanMethodsConfiguration.InnerConfiguration;
import org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzerTests.CyclicBeanMethodsConfiguration.InnerConfiguration.InnerInnerConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -47,7 +46,7 @@ import static org.junit.jupiter.api.Assertions.fail;
*/
class BeanCurrentlyInCreationFailureAnalyzerTests {
private final FailureAnalyzer analyzer = new BeanCurrentlyInCreationFailureAnalyzer();
private final BeanCurrentlyInCreationFailureAnalyzer analyzer = new BeanCurrentlyInCreationFailureAnalyzer();
@Test
void cyclicBeanMethods() throws IOException {
@ -131,6 +130,18 @@ class BeanCurrentlyInCreationFailureAnalyzerTests {
assertThat(this.analyzer.analyze(new BeanCurrentlyInCreationException("test"))).isNull();
}
@Test
void cycleWithCircularReferencesAllowed() throws IOException {
FailureAnalysis analysis = performAnalysis(CyclicBeanMethodsConfiguration.class, true);
assertThat(analysis.getAction()).contains("Despite circular references being allowed");
}
@Test
void cycleWithCircularReferencesProhibited() throws IOException {
FailureAnalysis analysis = performAnalysis(CyclicBeanMethodsConfiguration.class, false);
assertThat(analysis.getAction()).contains("As a last resort");
}
private List<String> readDescriptionLines(FailureAnalysis analysis) throws IOException {
try (BufferedReader reader = new BufferedReader(new StringReader(analysis.getDescription()))) {
return reader.lines().collect(Collectors.toList());
@ -138,13 +149,23 @@ class BeanCurrentlyInCreationFailureAnalyzerTests {
}
private FailureAnalysis performAnalysis(Class<?> configuration) {
FailureAnalysis analysis = this.analyzer.analyze(createFailure(configuration));
return performAnalysis(configuration, true);
}
private FailureAnalysis performAnalysis(Class<?> configuration, boolean allowCircularReferences) {
FailureAnalysis analysis = this.analyzer.analyze(createFailure(configuration, allowCircularReferences));
assertThat(analysis).isNotNull();
return analysis;
}
private Exception createFailure(Class<?> configuration) {
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(configuration)) {
private Exception createFailure(Class<?> configuration, boolean allowCircularReferences) {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(configuration);
AbstractAutowireCapableBeanFactory beanFactory = (AbstractAutowireCapableBeanFactory) context
.getBeanFactory();
this.analyzer.setBeanFactory(beanFactory);
beanFactory.setAllowCircularReferences(allowCircularReferences);
context.refresh();
fail("Expected failure did not occur");
return null;
}