After a discussion with @snicoll and @jhoeller insights, we found that it is possible to infer automatically JDK dynamic proxies required by Spring beans during the AOT processing.
Comment From: sdeleuze
@snicoll @jhoeller A draft implementation is available on gh-28980.
While transactional
and cache-simple-jdk-proxy
AOT smoke tests sample work without specific hints, validation
one fails with:
Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface jakarta.validation.Validator, interface org.springframework.aop.SpringProxy, interface org.springframework.aop.framework.Advised, interface org.springframework.core.DecoratingProxy] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles=<comma-separated-config-files> and -H:DynamicProxyConfigurationResources=<comma-separated-config-resources> options.
at com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:89) ~[na:na]
at com.oracle.svm.reflect.proxy.DynamicProxySupport.getProxyClass(DynamicProxySupport.java:158) ~[na:na]
at java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:48) ~[validation:na]
at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:1037) ~[validation:na]
at org.springframework.aop.framework.JdkDynamicAopProxy.getProxy(JdkDynamicAopProxy.java:126) ~[na:na]
at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110) ~[na:na]
at org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver.buildLazyResolutionProxy(ContextAnnotationAutowireCandidateResolver.java:130) ~[na:na]
at org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver.getLazyResolutionProxyIfNecessary(ContextAnnotationAutowireCandidateResolver.java:54) ~[na:na]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1292) ~[validation:6.0.0-SNAPSHOT]
at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveArgument(BeanInstanceSupplier.java:332) ~[na:na]
at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveArguments(BeanInstanceSupplier.java:265) ~[na:na]
at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:208) ~[na:na]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainInstanceFromSupplier(AbstractAutowireCapableBeanFactory.java:1225) ~[validation:6.0.0-SNAPSHOT]
Not sure yet why this one is not detected.
Comment From: sdeleuze
As discussed with Juergen, this is due to @Lazy
used there and will likely require a refinement to be potentially supported.
Comment From: jhoeller
I've addressed the specific MethodValidationPostProcessor
scenario through a setValidatorProvider(ObjectProvider<Validator>)
method, with the suggestion to adapt the injection point declaration in Boot's ValidationAutoConfiguration
accordingly.
Comment From: jhoeller
@sdeleuze for @Lazy
detection on injection points, we are not going to get a complete picture through the bean type determination in refreshForAotProcessing
. We'd have to fully initialize all beans for this since otherwise we won't hit all the - potentially nested - injection points. The key difference is that such lazy proxies are not created for managed bean instances themselves, they are rather locally created for adapting individual injection points in dependent beans.
Maybe we could cover the most common scenarios in our AOT configuration class processing, outside of the core container. As we are transforming each bean declaration into a programmatic bean definition, we could introspect the signatures of the factory methods, constructors and also setter methods that we're generating calls for - and automatically register a JDK proxy hint (or define a corresponding CGLIB subclass) for an @Lazy
injection point type. Alternatively, we could also ask people to change every such injection point to ObjectProvider
instead - which is generally the more efficient lazy access solution and therefore recommended in infrastructure code, but possibly not seen as convenient enough in application code.
Comment From: jhoeller
I've introduced a getLazyResolutionProxyClass
mechanism in the AutowireCandidateResolver
SPI, to be invoked for every parameter/field that we are triggering behind a specific bean definition. This works fine in a local test for both JDK proxies and CGLIB proxies. Hopefully it is straightforward to integrate into our AOT processing, @snicoll. For every Class returned, we should do something along the lines of the recent addition to refreshForAotProcessing
:
if (proxyType != null && Proxy.isProxyClass(proxyType)) {
runtimeHints.proxies().registerJdkProxy(proxyType.getInterfaces());
}
For CGLIB proxies, it should be sufficient to have triggered getLazyResolutionProxyClass
to begin with, capturing the generated class underneath the covers.
Comment From: snicoll
getLazyResolutionProxyClass
is integrated for @Autowired
fields and methods, as well as on the constructor or factory method of any bean in the context.