Skip to content

Infer JDK dynamic proxies for Spring beans #28980

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
sdeleuze opened this issue Aug 19, 2022 · 6 comments
Closed

Infer JDK dynamic proxies for Spring beans #28980

sdeleuze opened this issue Aug 19, 2022 · 6 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) theme: aot An issue related to Ahead-of-time processing type: enhancement A general enhancement
Milestone

Comments

@sdeleuze
Copy link
Contributor

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.

@sdeleuze sdeleuze added in: core Issues in core modules (aop, beans, core, context, expression) theme: aot An issue related to Ahead-of-time processing labels Aug 19, 2022
@sdeleuze sdeleuze added this to the 6.0.0-M6 milestone Aug 19, 2022
@sdeleuze sdeleuze self-assigned this Aug 19, 2022
sdeleuze added a commit to sdeleuze/spring-framework that referenced this issue Aug 22, 2022
sdeleuze added a commit to sdeleuze/spring-framework that referenced this issue Aug 22, 2022
sdeleuze added a commit to sdeleuze/spring-framework that referenced this issue Aug 22, 2022
Not required anymore since JDK dynamic proxies are inferred.

Closes spring-projectsgh-28980
@sdeleuze
Copy link
Contributor Author

@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.

@sdeleuze
Copy link
Contributor Author

As discussed with Juergen, this is due to @Lazy used there and will likely require a refinement to be potentially supported.

sdeleuze added a commit to sdeleuze/spring-framework that referenced this issue Aug 22, 2022
Further refinements will be required for
MethodValidationPostProcessor since @lazy
used by Spring Boot is not supported yet
for that use case.

See spring-projectsgh-28980
@jhoeller jhoeller added the type: enhancement A general enhancement label Aug 22, 2022
@jhoeller
Copy link
Contributor

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.

@jhoeller
Copy link
Contributor

@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.

@jhoeller
Copy link
Contributor

jhoeller commented Aug 22, 2022

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.

@snicoll
Copy link
Member

snicoll commented Aug 22, 2022

getLazyResolutionProxyClass is integrated for @Autowired fields and methods, as well as on the constructor or factory method of any bean in the context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) theme: aot An issue related to Ahead-of-time processing type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

3 participants