System
OS: macOS 11.4 JDK: AdoptOpenJDK-16.0.1+9 Gradle: 7.1.1 Spring Boot: 2.5.2
Demo
Run
gradle bootJar
java -jar build/libs/demo-0.0.1-SNAPSHOT.jar
Error
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'webSecurityConfig': Unsatisfied dependency expressed through method 'setContentNegotationStrategy' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration': Unsatisfied dependency expressed through method 'setConfigurers' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'webConfig': Unsatisfied dependency expressed through field 'requestMappingHandlerAdapter'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'requestMappingHandlerAdapter' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Unsatisfied dependency expressed through method 'requestMappingHandlerAdapter' parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'mvcContentNegotiationManager': Requested bean is currently in creation: Is there an unresolvable circular reference?
Comment From: wilkinsona
Thanks for the sample. I've reproduced the problem entirely on macOS. When run using java -jar
I see the BeanCurrentlyInCreationException
. When run with gradlew bootRun
or using the main method in my IDE the app starts successfully. The root cause of the problem is a circular dependency. In one scenario, Spring Framework is able to break this cycle but in the other it is not.
The cycle is due to your WebConfig
class. It is a WebMvcConfigurer
which means that it is a dependency of DelegatingWebMvcConfiguration
via its setConfigurers(List<WebMvcConfigurer> configurers)
method. It also depends on the RequestMappingHandlerAdapter
bean via its @Autowired
field. This bean is defined by WebMvcConfigurationSupport
, the super-class of DelegatingWebMvcConfiguration
.
The difference in behaviour is due to the different order in which the application's beans are processed. When webConfig
is processed first, Spring Framework is able to break this cycle. If you enable trace-level logging for org.springframework.beans
, you can see this cycle being broken via eager caching and reuse of the partially initialized bean:
2021-07-20 09:24:40.740 TRACE 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating instance of bean 'webConfig'
2021-07-20 09:24:40.741 TRACE 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Eagerly caching bean 'webConfig' to allow for resolving potential circular references
2021-07-20 09:24:40.742 DEBUG 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'requestMappingHandlerAdapter'
2021-07-20 09:24:40.742 TRACE 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating instance of bean 'requestMappingHandlerAdapter'
2021-07-20 09:24:40.742 DEBUG 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration'
2021-07-20 09:24:40.742 TRACE 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating instance of bean 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration'
2021-07-20 09:24:40.744 DEBUG 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'spring.resources-org.springframework.boot.autoconfigure.web.ResourceProperties'
2021-07-20 09:24:40.744 TRACE 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating instance of bean 'spring.resources-org.springframework.boot.autoconfigure.web.ResourceProperties'
2021-07-20 09:24:40.746 TRACE 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Eagerly caching bean 'spring.resources-org.springframework.boot.autoconfigure.web.ResourceProperties' to allow for resolving potential circular references
2021-07-20 09:24:40.746 TRACE 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'org.springframework.boot.context.properties.BoundConfigurationProperties'
2021-07-20 09:24:40.748 TRACE 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Finished creating instance of bean 'spring.resources-org.springframework.boot.autoconfigure.web.ResourceProperties'
2021-07-20 09:24:40.748 TRACE 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'spring.mvc-org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties'
2021-07-20 09:24:40.749 DEBUG 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'spring.web-org.springframework.boot.autoconfigure.web.WebProperties'
2021-07-20 09:24:40.749 TRACE 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating instance of bean 'spring.web-org.springframework.boot.autoconfigure.web.WebProperties'
2021-07-20 09:24:40.749 TRACE 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Eagerly caching bean 'spring.web-org.springframework.boot.autoconfigure.web.WebProperties' to allow for resolving potential circular references
2021-07-20 09:24:40.749 TRACE 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'org.springframework.boot.context.properties.BoundConfigurationProperties'
2021-07-20 09:24:40.750 TRACE 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Finished creating instance of bean 'spring.web-org.springframework.boot.autoconfigure.web.WebProperties'
2021-07-20 09:24:40.750 DEBUG 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Autowiring by type from bean name 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration' via constructor to bean named 'spring.resources-org.springframework.boot.autoconfigure.web.ResourceProperties'
2021-07-20 09:24:40.750 DEBUG 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Autowiring by type from bean name 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration' via constructor to bean named 'spring.mvc-org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties'
2021-07-20 09:24:40.750 DEBUG 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Autowiring by type from bean name 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration' via constructor to bean named 'spring.web-org.springframework.boot.autoconfigure.web.WebProperties'
2021-07-20 09:24:40.750 DEBUG 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Autowiring by type from bean name 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration' via constructor to bean named 'org.springframework.beans.factory.support.DefaultListableBeanFactory@43aaf813'
2021-07-20 09:24:40.754 TRACE 11173 --- [ main] o.s.beans.CachedIntrospectionResults : Getting BeanInfo for class [org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration]
2021-07-20 09:24:40.758 TRACE 11173 --- [ main] o.s.beans.CachedIntrospectionResults : Caching PropertyDescriptors for class [org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration]
2021-07-20 09:24:40.758 TRACE 11173 --- [ main] o.s.beans.CachedIntrospectionResults : Found bean property 'applicationContext' of type [org.springframework.context.ApplicationContext]
2021-07-20 09:24:40.758 TRACE 11173 --- [ main] o.s.beans.CachedIntrospectionResults : Found bean property 'class' of type [java.lang.Class]
2021-07-20 09:24:40.758 TRACE 11173 --- [ main] o.s.beans.CachedIntrospectionResults : Found bean property 'configurers' of type [java.util.List]
2021-07-20 09:24:40.758 TRACE 11173 --- [ main] o.s.beans.CachedIntrospectionResults : Found bean property 'resourceLoader' of type [org.springframework.core.io.ResourceLoader]
2021-07-20 09:24:40.758 TRACE 11173 --- [ main] o.s.beans.CachedIntrospectionResults : Found bean property 'servletContext' of type [javax.servlet.ServletContext]
2021-07-20 09:24:40.761 TRACE 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Eagerly caching bean 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration' to allow for resolving potential circular references
2021-07-20 09:24:40.762 TRACE 11173 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Returning eagerly cached instance of singleton bean 'webConfig' that is not fully initialized yet - a consequence of a circular reference
When webSecurityConfig
is processed first, the cycle changes slightly and Spring Framework is unable to break it.
webSecurityConfig
is a WebSecurityConfigurerAdapter
which requires a ContentNegotiationStrategy
bean via its setContentNegotationStrategy
method. This dependency is satisfied by the mvcContentNegotiationManager
bean defined by WebMvcConfigurationSupport
. To create the mvcContentNegotiationManager
bean, DelegatingWebMvcConfiguration
(the sub-class of WebMvcConfigurationSupport
) must be initialized first. This requires the creation of all WebMvcConfigurer
beans to satisfy its setConfigurers(List<WebMvcConfigurer> configurers)
method. We're now at the same point as we were in the cycle described above with WebConfig
being created and this requiring DelegatingWebMvcConfiguration
to have been initialized so that RequestMappingHandlerAdapter
can be injected into WebConfig
. Unlike the previous cycle, DelegatingWebMvcConfiguration
can't be eagerly cached as it hasn't been created yet so the cycle cannot be broken.
You can avoid the problem by ensuring that you don't inject any Spring MVC infrastructure (such as RequestMappingHandlerAdapter
) into a WebMvcConfigurer
as they are involved in configuring Spring MVC infrastructure.
With that said, I'd like to understand why the classpath ordering isn't consistent. Ideally, the class files should be written into the jar file in the exact same order as they appear on the classpath when using bootRun
. If this ordering was consistent, the behaviour at runtime would also be consistent.
Comment From: wilkinsona
When Spring Framework is resolving resources from jar files, it honours the ordering of the entries in the jar file and, by default, Gradle writes the files into the jar file in the order that they're provided to it by the file system. I believe this explains the different behaviour on macOS and Windows – the file systems on the two OSs are presenting the files to Gradle in two different orders so Gradle writes them into the jar files in two different orders. As a result, they're processed by Spring Framework in two different orders.
The difference between bootRun
and java -jar
on macOS is due to Spring Framework behaving differently when resolving resources from the file system, as it is in the bootRun
case. In this situation, its PathMatchingResourcePatternResolver
sorts the files in each directory by their names:
protected File[] listDirectory(File dir) {
File[] files = dir.listFiles();
if (files == null) {
if (logger.isInfoEnabled()) {
logger.info("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
}
return new File[0];
}
Arrays.sort(files, Comparator.comparing(File::getName));
return files;
}
In the example application, this causes WebConfig
to be processed before WebSecurityConfigurer
which is the reverse of the filesystem's default ordering of the two files.
You can make the behaviour more consistent by configuring Gradle to use a reproducible order for the files in the jar:
bootJar {
reproducibleFileOrder = true
}
This sorts BOOT-INF/classes
by path and WebConfig.class
therefore appears in the jar before WebSecurityConfig.class
. This isn't identical to Spring Framework's file system-based ordering but it's very close. It will also guarantee the same ordering of entries in jars built on different operating systems.
I think we should enable reproducibleFileOrder
by default for bootJar
but probably only in 2.6. There is a small chance that it will be a breaking change for those with circular dependencies that are relying on the current ordering for the cycle to be breakable.
I don't think this problem applies to Maven. Its jars are reproducible by default as of version 3.2.0 of the jar plugin.
Comment From: icuxika
@wilkinsona Thanks for your patience. I have tried to apply this configuration on my project and it works well now. Your reply will help me learn Spring Framework better.
Comment From: wilkinsona
@icuxika You're welcome. I've re-opened the issue as I'd like to use it to enable reproducible ordering by default.
Comment From: wilkinsona
I don't think this problem applies to Maven. Its jars are reproducible by default as of version 3.2.0 of the jar plugin.
I was mistaken here. You have to opt in with Maven by setting project.build.outputTimestamp
.
When opted in with Maven, entries beneath BOOT-INF/classes
are ordered but the order of jars beneath BOOT-INF/lib
is unchanged. By contrast, if you're building a war project, the entries beneath WEB-INF/lib
are ordered.
Given that this is opt-in with Maven I'm not so sure that we should enable reproducible ordering by default with Gradle. I also think we should probably change repackaging with Maven so that its ordering behaviour aligns with the war plugin.
Comment From: wilkinsona
We discussed this today and decided not to do anything by default. https://github.com/spring-projects/spring-boot/issues/27436 has been opened to align the behaviour of a repackaged fat jar with Maven's war plugin.