While investigating the cause of the regression reported in #31238, I noticed that Spring Framework makes multiple attempts to create CGLIB proxy classes for @Configuration
classes before actually instantiating the proxy instance.
For example, if you run the transactionProxyIsCreated()
test method in EnableTransactionManagementTests
and introduce tracing for proxy classes generated, you will see something similar to the following.
loadClass: org.springframework.transaction.annotation.EnableTransactionManagementTests$TxManagerConfig$$SpringCGLIB$$0
loadClass: org.springframework.transaction.annotation.EnableTransactionManagementTests$TxManagerConfig$$SpringCGLIB$$1
defineClass: class org.springframework.transaction.annotation.EnableTransactionManagementTests$TxManagerConfig$$SpringCGLIB$$1
loadClass: org.springframework.transaction.annotation.EnableTransactionManagementTests$TxManagerConfig$$SpringCGLIB$$2
defineClass: class org.springframework.transaction.annotation.EnableTransactionManagementTests$TxManagerConfig$$SpringCGLIB$$2
defineClass: class org.springframework.transaction.annotation.EnableTransactionManagementTests$TxManagerConfig$$SpringCGLIB$$0
>>>> class org.springframework.transaction.annotation.EnableTransactionManagementTests$TxManagerConfig$$SpringCGLIB$$0
The last line (>>>> class ...
) denotes the actual generated class ($TxManagerConfig$$SpringCGLIB$$0
) that was used to instantiate the proxy for the TxManagerConfig
configuration class; however, $TxManagerConfig$$SpringCGLIB$$1
and $TxManagerConfig$$SpringCGLIB$$2
were also created in the interim even though they were not used in the end.
In addition, all of the generated proxy classes for a given @Configuration
class are recorded by Spring AOT and stored in a GraalVM native image.
Ideally, those interim classes should not be generated. Thus, it appears that there is an issue with cache key, and this appears to be a regression.
The following can be used to track which proxy classes are loaded.
ReflectUtils.setLoadedClassHandler(clazz -> System.out.println("loadClass: " + clazz.getName()));
Comment From: sbrannen
After thorough analysis, I have determined that there are no duplicate CGLIB proxy classes generated for @Configuration
classes.
If a @Configuration
class does not contain any @Bean
methods, only a single class is generated by CGLIB, and that class is the proxy class.
On the other hand, if a @Configuration
class contains @Bean
methods, 2 additional classes are generated by CGLIB. These are FastClass
implementations that are required to support super.myBeanMethod()
invocations which cannot be achieved via standard JDK reflection.
6.0.x
When running my test on 6.0.x
, it appears that 3 proxy classes are generated.
org.example.AppConfig$$SpringCGLIB$$0
org.example.AppConfig$$SpringCGLIB$$1
org.example.AppConfig$$SpringCGLIB$$2
5.3.x
When running the same test on 5.3.x
, the generated class names provide a better picture of what's going on.
org.example.AppConfig$$EnhancerBySpringCGLIB$$fd7e9baa
org.example.AppConfig$$FastClassBySpringCGLIB$$3fec86e
org.example.AppConfig$$EnhancerBySpringCGLIB$$fd7e9baa$$FastClassBySpringCGLIB$$82534900
In light of that, I am repurposing this issue to investigate whether we can reintroduce FastClass
into the generated class names (when appropriate) while retaining the reproducibility of generated proxy class names that was introduced in commit b31a15851e7aaabf3629cc101d285e751e535927.