Description
When you generate CGLib proxy classes during the AOT phase by using the ProxyFactory
, those classes will be picked up at runtime, but also need to be made available for reflection, as EnhancerFactoryData
's constructor will still try to lookup CGLib generated methods at runtime via reflection, and also actually try to call those during instance creation (EnhancerFactoryData.newInstance(…)
).
Caused by: org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class org.springsource.restbucks.drinks.Drink: Common causes of this problem include using a final class or a non-visible class
at org.springframework.aop.framework.CglibAopProxy.buildProxy(CglibAopProxy.java:216) ~[restbucks:6.0.0-SNAPSHOT]
at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:158) ~[restbucks:6.0.0-SNAPSHOT]
at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110) ~[na:na]
at org.springframework.data.util.MethodInvocationRecorder.create(MethodInvocationRecorder.java:100) ~[restbucks:3.0.0-SNAPSHOT]
at org.springframework.data.util.MethodInvocationRecorder.forProxyOf(MethodInvocationRecorder.java:76) ~[restbucks:3.0.0-SNAPSHOT]
at org.springframework.data.domain.Sort$TypedSort.<init>(Sort.java:645) ~[na:na]
at org.springframework.data.domain.Sort.sort(Sort.java:143) ~[restbucks:3.0.0-SNAPSHOT]
at org.springsource.restbucks.drinks.DrinksOptions.<clinit>(DrinksOptions.java:39) ~[restbucks:na]
... 27 common frames omitted
Caused by: org.springframework.cglib.core.CodeGenerationException: java.lang.NoSuchMethodException-->org.springsource.restbucks.drinks.Drink$$SpringCGLIB$$0.CGLIB$SET_THREAD_CALLBACKS([Lorg.springframework.cglib.proxy.Callback;)
at org.springframework.cglib.proxy.Enhancer$EnhancerFactoryData.<init>(Enhancer.java:506) ~[na:na]
at org.springframework.cglib.proxy.Enhancer.wrapCachedClass(Enhancer.java:801) ~[na:na]
This can be currently worked around by also explicitly registering reflection hints for the types that proxies have been created for.
class MyRegistrar implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
var factory = new ProxyFactory();
factory.setTargetClass(Drink.class);
factory.setProxyTargetClass(true);
hints.reflection().registerType(factory.getProxyClass(classLoader), MemberCategory.INVOKE_DECLARED_METHODS);
}
}
It also looks like multiple invocations of same the ProxyFactory
setup would create multiple CGLib proxy classes. So if application code accidently invokes a similar arrangement e.g. during the initialization of a static field (use case here: defining a static typed, Spring Data Sort
instance via Sort.sort(MyType.class).by(MyType::getName)
and the initial step requiring the creation of a proxy instance), the reflection information will be registered for the other proxy class and the arrangement will still fail at runtime, as the hints have not been registered for the type created by the statically initialized field.