Skip to content

Commit 31d88c3

Browse files
committed
Improve failure analysis action when circular references are allowed
Closes gh-27735
1 parent 42ef97b commit 31d88c3

File tree

2 files changed

+54
-13
lines changed

2 files changed

+54
-13
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BeanCurrentlyInCreationFailureAnalyzer.java

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@
1919
import java.util.ArrayList;
2020
import java.util.List;
2121

22+
import org.springframework.beans.BeansException;
2223
import org.springframework.beans.factory.BeanCreationException;
2324
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
25+
import org.springframework.beans.factory.BeanFactory;
26+
import org.springframework.beans.factory.BeanFactoryAware;
2427
import org.springframework.beans.factory.InjectionPoint;
2528
import org.springframework.beans.factory.UnsatisfiedDependencyException;
29+
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
2630
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
2731
import org.springframework.boot.diagnostics.FailureAnalysis;
2832
import org.springframework.util.StringUtils;
@@ -33,20 +37,36 @@
3337
*
3438
* @author Andy Wilkinson
3539
*/
36-
class BeanCurrentlyInCreationFailureAnalyzer extends AbstractFailureAnalyzer<BeanCurrentlyInCreationException> {
40+
class BeanCurrentlyInCreationFailureAnalyzer extends AbstractFailureAnalyzer<BeanCurrentlyInCreationException>
41+
implements BeanFactoryAware {
42+
43+
private AbstractAutowireCapableBeanFactory beanFactory;
44+
45+
@Override
46+
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
47+
if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
48+
this.beanFactory = (AbstractAutowireCapableBeanFactory) beanFactory;
49+
}
50+
}
3751

3852
@Override
3953
protected FailureAnalysis analyze(Throwable rootFailure, BeanCurrentlyInCreationException cause) {
4054
DependencyCycle dependencyCycle = findCycle(rootFailure);
4155
if (dependencyCycle == null) {
4256
return null;
4357
}
44-
return new FailureAnalysis(buildMessage(dependencyCycle),
45-
"Relying upon circular references is discouraged and they are prohibited by default. "
46-
+ "Update your application to remove the dependency cycle between beans. "
47-
+ "As a last resort, it may be possible to break the cycle automatically by setting "
48-
+ "spring.main.allow-circular-references to true if you have not already done so.",
49-
cause);
58+
return new FailureAnalysis(buildMessage(dependencyCycle), action(), cause);
59+
}
60+
61+
private String action() {
62+
if (this.beanFactory != null && this.beanFactory.isAllowCircularReferences()) {
63+
return "Despite circular references being allowed, the dependency cycle between beans could not be "
64+
+ "broken. Update your application to remove the dependency cycle.";
65+
}
66+
return "Relying upon circular references is discouraged and they are prohibited by default. "
67+
+ "Update your application to remove the dependency cycle between beans. "
68+
+ "As a last resort, it may be possible to break the cycle automatically by setting "
69+
+ "spring.main.allow-circular-references to true.";
5070
}
5171

5272
private DependencyCycle findCycle(Throwable rootFailure) {

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/BeanCurrentlyInCreationFailureAnalyzerTests.java

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,12 @@
2626

2727
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
2828
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
2930
import org.springframework.boot.diagnostics.FailureAnalysis;
30-
import org.springframework.boot.diagnostics.FailureAnalyzer;
3131
import org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzerTests.CycleWithAutowiredFields.BeanThreeConfiguration;
3232
import org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzerTests.CycleWithAutowiredFields.BeanTwoConfiguration;
3333
import org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzerTests.CyclicBeanMethodsConfiguration.InnerConfiguration;
3434
import org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzerTests.CyclicBeanMethodsConfiguration.InnerConfiguration.InnerInnerConfiguration;
35-
import org.springframework.context.ConfigurableApplicationContext;
3635
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
3736
import org.springframework.context.annotation.Bean;
3837
import org.springframework.context.annotation.Configuration;
@@ -47,7 +46,7 @@
4746
*/
4847
class BeanCurrentlyInCreationFailureAnalyzerTests {
4948

50-
private final FailureAnalyzer analyzer = new BeanCurrentlyInCreationFailureAnalyzer();
49+
private final BeanCurrentlyInCreationFailureAnalyzer analyzer = new BeanCurrentlyInCreationFailureAnalyzer();
5150

5251
@Test
5352
void cyclicBeanMethods() throws IOException {
@@ -131,20 +130,42 @@ void cycleWithAnUnknownStartIsNotAnalyzed() {
131130
assertThat(this.analyzer.analyze(new BeanCurrentlyInCreationException("test"))).isNull();
132131
}
133132

133+
@Test
134+
void cycleWithCircularReferencesAllowed() throws IOException {
135+
FailureAnalysis analysis = performAnalysis(CyclicBeanMethodsConfiguration.class, true);
136+
assertThat(analysis.getAction()).contains("Despite circular references being allowed");
137+
}
138+
139+
@Test
140+
void cycleWithCircularReferencesProhibited() throws IOException {
141+
FailureAnalysis analysis = performAnalysis(CyclicBeanMethodsConfiguration.class, false);
142+
assertThat(analysis.getAction()).contains("As a last resort");
143+
}
144+
134145
private List<String> readDescriptionLines(FailureAnalysis analysis) throws IOException {
135146
try (BufferedReader reader = new BufferedReader(new StringReader(analysis.getDescription()))) {
136147
return reader.lines().collect(Collectors.toList());
137148
}
138149
}
139150

140151
private FailureAnalysis performAnalysis(Class<?> configuration) {
141-
FailureAnalysis analysis = this.analyzer.analyze(createFailure(configuration));
152+
return performAnalysis(configuration, true);
153+
}
154+
155+
private FailureAnalysis performAnalysis(Class<?> configuration, boolean allowCircularReferences) {
156+
FailureAnalysis analysis = this.analyzer.analyze(createFailure(configuration, allowCircularReferences));
142157
assertThat(analysis).isNotNull();
143158
return analysis;
144159
}
145160

146-
private Exception createFailure(Class<?> configuration) {
147-
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(configuration)) {
161+
private Exception createFailure(Class<?> configuration, boolean allowCircularReferences) {
162+
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
163+
context.register(configuration);
164+
AbstractAutowireCapableBeanFactory beanFactory = (AbstractAutowireCapableBeanFactory) context
165+
.getBeanFactory();
166+
this.analyzer.setBeanFactory(beanFactory);
167+
beanFactory.setAllowCircularReferences(allowCircularReferences);
168+
context.refresh();
148169
fail("Expected failure did not occur");
149170
return null;
150171
}

0 commit comments

Comments
 (0)