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.