Affects: Spring 6.0.11 / Spring Boot 3.1.2
For reproducing the issue, I highly recommend starting with the attached reproducer linked below.
Assume an application with the following components: - Spring Boot 3.1.2 - Spring JPA - Spring Security - Spring Web - Spring test - Spring Security test - H2 as a database (for tests, I don't think the DB affects this issue)
Let this application have a controller with an endpoint accepting a MultipartFile
RequestParam
that calls an @Transactional
method.
Add an entity with a String
field (@Id
) and a CrudRepository
with a getBy<The string field>
.
Add an ApplicationListener<ApplicationReadyEvent>
which calls the above method.
Add a @SpringBootTest
with two @Test
methods, one of them annotated with @WithMockUser
. These test methods can be empty.
Run the tests using native-image
with mvn -PnativeTest test
. An exception like the following should occur:
java.lang.IllegalStateException: Failed to load ApplicationContext for [AotMergedContextConfiguration@5207f8d0 testClass = io.github.danthe1st.spring_test.SpringTestApplicationTests, contextInitializerClass = io.github.danthe1st.spring_test.SpringTestApplicationTests__TestContext001_ApplicationContextInitializer, original = [WebMergedContextConfiguration@6b119ba0 testClass = io.github.danthe1st.spring_test.SpringTestApplicationTests, locations = [], classes = [io.github.danthe1st.spring_test.SpringTestApplication], contextInitializerClasses = [], activeProfiles = [], propertySourceLocations = [], propertySourceProperties = ["org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@1f, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@64e953ce, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@29866a5b, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@329bad59, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@24c2dbca, org.springframework.boot.test.context.SpringBootTestAnnotation@6c69f2e0], resourceBasePath = "src/main/webapp", contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null]]
org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:143)
org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:127)
org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:191)
org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:130)
org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:241)
[...]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'someController': Unexpected AOP exception
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:605)
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520)
org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326)
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324)
[...]
Caused by: org.springframework.aop.framework.AopConfigException: Unexpected AOP exception
org.springframework.aop.framework.CglibAopProxy.buildProxy(CglibAopProxy.java:228)
org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:155)
org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110)
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.buildProxy(AbstractAutoProxyCreator.java:517)
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy(AbstractAutoProxyCreator.java:464)
[...]
Caused by: java.lang.UnsupportedOperationException: CGLIB runtime enhancement not supported on native image. Make sure to include a pre-generated class on the classpath instead: io.github.danthe1st.spring_test.SomeController$$SpringCGLIB$$1
org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:575)
org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.lambda$new$1(AbstractClassGenerator.java:107)
org.springframework.cglib.core.internal.LoadingCache.lambda$createEntry$1(LoadingCache.java:52)
java.base@17.0.5/java.util.concurrent.FutureTask.run(FutureTask.java:264)
[...]
Reproducer: https://github.com/danthe1st/spring-cglib-repro/actions/runs/5648996009 Build log with error: https://github.com/danthe1st/spring-cglib-repro/actions/runs/5648996009/job/15302392597
This issue is similar to #30937 but the error is different so I decided to report both issues.
Comment From: snicoll
same as https://github.com/spring-projects/spring-framework/issues/30937#issuecomment-1649166515
Comment From: danthe1st
When trying to reproduce the issue, it only happened with @Transactional
(without it, I got to the other issue).
MultipartFile
is actually not required (I thought it was required because removing it caused the issue to disappear at some point in the process of creating the reproducer).
I also added the enhancement plugin to the reproducer (this doesn't change anything and I have previously removed it so I can reduce the reproducer as much as possible).
I assume that @Transactional
causes the proxy class to be required/generated (at runtime).
The reproducer is now updated. The other issue is a bit different, I'll do similar steps there as well.
Comment From: snicoll
Thanks. The runtime expects io.github.danthe1st.spring_test.SomeController$$SpringCGLIB$$1
but only io.github.danthe1st.spring_test.SomeController$$SpringCGLIB$$0
has been generated. It looks like a second proxy is required that isn't discovered by AOT.
@jhoeller any idea how we could debug this?
Comment From: danthe1st
Output of the mvn test -PnativeTest
command (for the future in case the GitHub Actions log expired):
build.log
Comment From: danthe1st
Is there any chance in getting this finding out how this can be debugged/finding out why the second proxy would be necessary/why it isn't generated? To me, this seems like an issue that would be applicable in many cases.
Comment From: sbrannen
@danthe1st, what happens if you remove hibernate-enhance-maven-plugin
from pom.xml
?
Comment From: danthe1st
Originally, I didn't have the hibernate enhancer plugin in my reproducer and I just added it after https://github.com/spring-projects/spring-framework/issues/30939#issuecomment-1649380868 but I will update it.
Comment From: danthe1st
I have removed the plugin in a branch named cglib-no-hibernate-enhancer
in my reproducer. You can find the build log here.
As you can see, the error still occurs.
@sbrannen
Comment From: snicoll
@danthe1st to answer your question, we'll have to debug it to understand why the second proxy was not generated at build-time. It's high on my list and I hope to get to that next week.
Comment From: sbrannen
we'll have to debug it to understand why the second proxy was not generated at build-time. It's high on my list and I hope to get to that next week.
I believe this may actually be a duplicate of #31238 (see https://github.com/spring-projects/spring-framework/issues/31238#issuecomment-1722461876).
In any case, I think the underlying problem is that a new proxy class is generated for (what should be) the same key, which might imply that there is something wrong with the key we generate.
Comment From: danthe1st
I want to note that in contrast to #31238, this issue only seems to occur with Spring Boot 3.1.x and not with 3.0.x (if I remember correctly)
Comment From: sbrannen
I have confirmed that the local fix I have in place for #31238 allows the native image tests for the example project for this issue to pass using Spring Boot 3.1.2 and Spring Framework 6.0.13-SNAPSHOT.
In light of that, I am closing this issue as a:
- Duplicate of #31238
Comment From: sbrannen
It turns out this issue was not only a duplicate of #31238 but also a:
- Duplicate of #31050
Although I knew part of this issue was resolved in 6.0.13-SNAPSHOT, I wasn't sure what had actually caused the second attempt to create the @Transactional
proxy class; whereas, in #31238 it was clearly due to the use of @DirtiesContext
.
So I decided to debug the sample application for this issue further, and in doing so I noticed that #31050 was causing the first attempt to load the test's ApplicationContext
to fail, and that's why the second @Test
method resulted in a 2nd attempt to load the ApplicationContext
, thereby displaying the same behavior as in #31238.
With that, the mystery is solved!