DelegatingWebFluxConfiguration
can trigger BeanCurrentlyInCreationException
under a few circumstances.
ReactiveAdapterRegistry
The first problem is that it is difficult to provide a HandlerMethodArgumentResolver
that needs the ReactiveAdapterRegistry
because this can cause a cycle depending on the order the beans are instantiated. For example, this test produces a cycle:
authenticationPrincipalArgumentResolver
is requested- It triggers the creation of
ReactiveAdapterRegistry
which is a dependant bean - In order to create
ReactiveAdapterRegistry
,DelegatingWebFluxConfiguration
needs to be created which requires allWebFluxConfigurer
instances to be wired into it. - This tries to retrieve all
WebFluxConfigurer
instances. One of which isauthenticationPrincipalArgumentResolverConfigurer
which provides ourauthenticationPrincipalArgumentResolver
bean as an argument resolver. - This requests
authenticationPrincipalArgumentResolver
which is a cycle
One option to make this less likely to happen is to define authenticationPrincipalArgumentResolverConfigurer
as:
@Bean
public WebFluxConfigurer authenticationPrincipalArgumentResolverConfigurer(
ObjectProvider<AuthenticationPrincipalArgumentResolver> authenticationPrincipalArgumentResolver) {
return new WebFluxConfigurer() {
@Override
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
configurer.addCustomResolver(authenticationPrincipalArgumentResolver.getObject());
}
};
}
This delays the lookup of AuthenticationPrincipalArgumentResolver
. However, this has a few drawbacks.
- The first drawback is that while it reduces the likelihood of an error, it doesn't remove the cycle between
ArgumentResolverConfig
andWebFluxConfigurationSupport
. - Second, it requires every
AuthenticationPrincipalArgumentResolver
configuration needingReactiveAdapterRegistry
(I'd argue that is pretty much everyAuthenticationPrincipalArgumentResolver
) to have the burden of fixing the issue rather than decoupling the beans (HandlerMethodArgumentResolver
andReactiveAdapterRegistry
) that are likely dependant on one another).
We could move the delay inside DelegatingWebFluxConfiguration
by autowiring ObjectProvider<WebFluxConfigurer>
instead. However, this only fixes the second problem and leaves the cycle present.
Another option is to make the definition of ReactiveAdapterRegistry
a static method. This would ensure that DelegatingWebFluxConfiguration
does not need to be instantiated. Yet another option would be to move ReactiveAdapterRegistry
to another configuration, but this could break other applications.
ResourceUrlProvider Can Cause Early Initialization of DelegatingWebFluxConfiguration
ResourceUrlProvider
implements ApplicationListener
which can trigger early initialization of DelegatingWebFluxConfiguration
. As soon as an ApplicationEvent
is published, the ApplicationListener
s need initialized. Since ResourceUrlProvider
is defined by DelegatingWebFluxConfiguration
it initializes DelegatingWebFluxConfiguration
and all of its dependant beans very early on.
This is what is why the originally reported issue would happen in Boot 2.3.0 and not in previous versions. Specifically the changes for https://github.com/spring-projects/spring-boot/issues/21325 moved the publishing of the ReactiveWebServerInitializedEvent
from finishRefresh to SmartLifecycle
's start
method. This changed the order that DelegatingWebFluxConfiguration
was created and triggered the bean cycle to happen.
One solution is to move the ResourceUrlProvider
bean definition to a static method.
Related Issues
- https://github.com/spring-projects/spring-boot/issues/21580
- https://github.com/spring-projects/spring-security/issues/8596
Comment From: THS-on
We currently run into this issue. Is there a workaround that we could apply?
It seems that it's really inconsistent when it happens. gradle bootRun
always works but building a jar and starting it works on one machine and fails on on others.
Gradle config versions
id("org.springframework.boot") version "2.3.3.RELEASE"
id("io.spring.dependency-management") version "1.0.10.RELEASE"
kotlin("jvm") version "1.3.72"
kotlin("plugin.spring") version "1.3.72"