Add cycles to core-beans: the circular-dependency failure report, what allow-circular-references repairs and hides, and four fixes on Boot 4.1

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Uu7q8vPeREyT4218EJPzz1
This commit is contained in:
Claude
2026-09-24 06:29:14 +00:00
parent 51f3ecd5da
commit d6a2e1a5f2
61 changed files with 1545 additions and 8 deletions
@@ -0,0 +1,22 @@
# Three constructor-injected services in a ring: what Spring Boot 4.1.1 prints when start-up fails
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| notificationService defined in file [<core-beans>/target/classes/com/ankurm/corebeans/cycles/broken/NotificationService.class]
↑ ↓
| orderService defined in file [<core-beans>/target/classes/com/ankurm/corebeans/cycles/broken/OrderService.class]
↑ ↓
| paymentService defined in file [<core-beans>/target/classes/com/ankurm/corebeans/cycles/broken/PaymentService.class]
└─────┘
Action:
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.
@@ -0,0 +1,14 @@
# The exception behind the report, and the order in which the container started creating beans
--- exception classes, outermost first ---
UnsatisfiedDependencyException -> UnsatisfiedDependencyException -> UnsatisfiedDependencyException -> BeanCurrentlyInCreationException
--- root cause ---
org.springframework.beans.factory.BeanCurrentlyInCreationException
Error creating bean with name 'notificationService': Requested bean is currently in creation: Is there an unresolvable circular reference or an asynchronous initialization dependency?
--- creation order (one line per bean the container began to create) ---
start creating notificationService
start creating orderService
start creating paymentService
@@ -0,0 +1,20 @@
# A two-bean cycle written as @Bean methods: the report names the configuration class instead of a class file
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| clock defined in com.ankurm.corebeans.cycles.beanmethods.BeanMethodCycle
↑ ↓
| calendar defined in com.ankurm.corebeans.cycles.beanmethods.BeanMethodCycle
└─────┘
Action:
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.
@@ -0,0 +1,5 @@
# spring.main.lazy-initialization=true on the same three-service ring
context started: true
getBean(OrderService.class) failed: UnsatisfiedDependencyException -> UnsatisfiedDependencyException -> UnsatisfiedDependencyException -> BeanCurrentlyInCreationException
root cause: Error creating bean with name 'orderService': Requested bean is currently in creation: Is there an unresolvable circular reference or an asynchronous initialization dependency?
@@ -0,0 +1,15 @@
# A field-injected cycle with spring.main.allow-circular-references=true: what each @PostConstruct sees
--- flag off (the Boot default) ---
started: false
root cause: Error creating bean with name 'firstService': Requested bean is currently in creation: Is there an unresolvable circular reference or an asynchronous initialization dependency?
--- flag on ---
started: true
start creating firstService
FirstService constructed
start creating secondService
SecondService constructed
SecondService @PostConstruct sees first.isReady() = false
FirstService @PostConstruct done, ready=true
@@ -0,0 +1,21 @@
# The same field cycle, with @Async on AsyncFirst and the flag on: start-up versus a lazy first getBean
--- eager start-up (the default) ---
start creating com.ankurm.corebeans.cycles.allowed.AsyncFirst
start creating com.ankurm.corebeans.cycles.allowed.AsyncSecond
AsyncSecond constructed, identity 2f0ed952
AsyncSecond.setFirst on instance 2f0ed952 received a proxy: false
AsyncSecond constructed, identity 5a8816cc
AsyncSecond.setFirst on instance 5a8816cc received a proxy: true
started: true
AsyncFirst bean is a proxy : true
AsyncSecond.first is a proxy : true
AsyncSecond.first == the bean : true
log line from DefaultListableBeanFactory (INFO):
Bean 'com.ankurm.corebeans.cycles.allowed.AsyncFirst' marked for pre-instantiation (not lazy-init) but currently initialized by other thread - skipping it in mainline thread
--- spring.main.lazy-initialization=true, then getBean(AsyncFirst.class) ---
started: true
getBean(AsyncFirst.class) failed: BeanCurrentlyInCreationException
root cause: Error creating bean with name 'com.ankurm.corebeans.cycles.allowed.AsyncFirst': Bean with name 'com.ankurm.corebeans.cycles.allowed.AsyncFirst' has been injected into other beans [com.ankurm.corebeans.cycles.allowed.AsyncSecond] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.
@@ -0,0 +1,7 @@
# The same field cycle, with @Cacheable on one of the two beans, and the flag on
started: true
CachedFirst bean is a proxy : true (CachedFirst$$SpringCGLIB$$0)
CachedSecond.first is a proxy : true
CachedSecond.first == the bean : true
two calls through the injected reference return 1 and 1, method body ran 1 time(s)
+4
View File
@@ -0,0 +1,4 @@
# Fix 1: extract OrderCatalog, the one thing NotificationService needed
started: true
place("kettle") -> order placed: charged, e-mail says: 1 x kettle
+8
View File
@@ -0,0 +1,8 @@
# Fix 2: PaymentService publishes an event instead of calling NotificationService
started: true
place("kettle") -> order placed: charged
listener on thread main: e-mail says: 1 x kettle
--- what the publisher sees when the listener throws ---
place("boom") threw IllegalStateException: mail server down
+5
View File
@@ -0,0 +1,5 @@
# Fix 3: @Lazy on NotificationService's OrderService parameter
started: true
NotificationService holds a com.ankurm.corebeans.cycles.lazy.OrderService$$SpringCGLIB$$0
place("kettle") -> order placed: charged, e-mail says: 1 x kettle
@@ -0,0 +1,12 @@
# Fix 4: ObjectProvider<OrderService> in NotificationService
started: true
place("kettle") -> order placed: charged, e-mail says: 1 x kettle
--- the same provider, but getObject() is called inside the constructor ---
started: false
root cause: Error creating bean with name 'cycleFixesTest.EagerNotifier': Requested bean is currently in creation: Is there an unresolvable circular reference or an asynchronous initialization dependency?
--- the provider needs no proxy, so a final class is fine ---
started: true
hello() -> hello
@@ -0,0 +1,5 @@
# @Lazy on a constructor parameter whose type is a final class
started: false
exception classes: BeanCreationException -> AopConfigException -> IllegalArgumentException
root cause: java.lang.IllegalArgumentException: Cannot subclass final class com.ankurm.corebeans.CycleFixesTest$FinalTarget
@@ -0,0 +1,11 @@
# @Lazy on the injection point, with a target that cannot be built
--- @Lazy on the parameter, target is an ordinary singleton ---
started: false
root cause: cannot reach the payment provider
--- @Lazy on the parameter AND on the target bean ---
started: true
first call threw: BeanCreationException -> BeanInstantiationException -> IllegalStateException
root cause: cannot reach the payment provider
@@ -0,0 +1,11 @@
# Dispatcher(List<Handler>) and a Handler that needs the Dispatcher
--- Handler injects the Dispatcher ---
started: false
exception classes: UnsatisfiedDependencyException -> UnsatisfiedDependencyException -> BeanCurrentlyInCreationException
root cause: Error creating bean with name 'hiddenCyclesTest.Dispatcher': Requested bean is currently in creation: Is there an unresolvable circular reference or an asynchronous initialization dependency?
--- Handler injects ObjectProvider<Dispatcher> ---
started: true
dispatch("ping") -> [seen ping (dispatcher available: true)]
@@ -0,0 +1,5 @@
# @DependsOn("b") on a and @DependsOn("a") on b: no injection involved at all
started: false
exception classes: BeanCreationException
root cause: Error creating bean with name 'b' defined in com.ankurm.corebeans.HiddenCyclesTest$DependsOnCycle: Circular depends-on relationship between 'b' and 'a'
@@ -0,0 +1,5 @@
# @Lazy on the parameter, but the bean calls the dependency from @PostConstruct
started: false
exception classes: BeanCreationException -> UnsatisfiedDependencyException -> BeanCurrentlyInCreationException
root cause: Error creating bean with name 'cycleFixesTest.LazyUserA': Requested bean is currently in creation: Is there an unresolvable circular reference or an asynchronous initialization dependency?
@@ -0,0 +1,4 @@
# Defaults read from spring-configuration-metadata.json in spring-boot-4.1.1.jar
spring.main.allow-circular-references default=False
spring.main.lazy-initialization default=False
@@ -0,0 +1,23 @@
# Who hands out an early reference, and the handler in preInstantiateSingleton (Spring Framework 7.0.9 jars)
--- the class that draws the report, in spring-boot-4.1.1.jar ---
org/springframework/boot/diagnostics/analyzer/BeanCurrentlyInCreationFailureAnalyzer$BeanInCycle.class
org/springframework/boot/diagnostics/analyzer/BeanCurrentlyInCreationFailureAnalyzer$DependencyCycle.class
org/springframework/boot/diagnostics/analyzer/BeanCurrentlyInCreationFailureAnalyzer.class
--- AbstractAutoProxyCreator (spring-aop): declared methods that mention 'early' ---
private final java.util.Map<java.lang.Object, java.lang.Object> earlyBeanReferences;
public java.lang.Object getEarlyBeanReference(java.lang.Object, java.lang.String);
--- AbstractAdvisingBeanPostProcessor (spring-aop): declared members that mention 'early' ---
matches: 0
--- the @Async post-processor's ancestry ---
class org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor extends org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor
class org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor extends org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor
--- DefaultListableBeanFactory.preInstantiateSingleton(String, RootBeanDefinition): exception table ---
Exception table:
from to target type
157 162 165 Class org/springframework/beans/factory/BeanCurrentlyInCreationException