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:
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user