spring-framework
spring-framework copied to clipboard
AOP support in native images, beyond simple method expressions
So this is a continuation of https://github.com/spring-projects-experimental/spring-native/issues/956 which is still an open issue with Boot 3 (RC2 + latest snapshot)
While simple methods expressions like this just work fine "execution(public * org.goafabric.calleeservice.aspect.TestComponent.*(..))"
More complex expressions, that e.g. leverage an custom anntotation, don't. To be more specific .. they work with console applications, but crash the moment wie have WEB on the classpath. Which somehow explains the error. Tried to set the jdk proxy interfaces that graalvm demands, but stopped after the 3rd roundtrip because this seems to be a neverending road ...
I've attached a simple example that works with non native, but crashes on bootstrap of the native image. What's also a little mind boggeling .. is that i have to register a simple hint for the Test Aspect. While the aot-smoke-tests don't ...
@goafabric thanks but that's not a Spring Boot concern. Moving to framework.
- Potentially a duplicate of #28711
Thx for answering that fast .. insanely fast ...
https://github.com/spring-projects/spring-framework/issues/28711 is a little different
It at least explains why i had to set
hints.reflection().registerType(TestAspect.class, MemberCategory.INVOKE_DECLARED_METHODS)
.
But with this the invocation works .. for all use cases as long as it is a simple console application.
Combine it with web and it crashes. So I guess this is a little different.
That's an interesting one. We have:
@Component
@Aspect
public class TestAspect {
@Around("@within(org.goafabric.calleeservice.aspect.TestAnnotation)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
try {
return joinPoint.proceed();
} finally {
final Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
System.err.println("aspects wrapped method : " + toString(method));
}
}
// ...
}
The sample indeed fails with errors like:
Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory, interface org.springframework.context.ResourceLoaderAware, interface org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory, interface org.springframework.boot.web.server.ConfigurableWebServerFactory] 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 org.graalvm.nativeimage.builder/com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:89) ~[na:na]
at org.graalvm.nativeimage.builder/com.oracle.svm.core.reflect.proxy.DynamicProxySupport.getProxyClass(DynamicProxySupport.java:171) ~[na:na]
at [email protected]/java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:47) ~[callee-service:na]
at [email protected]/java.lang.reflect.Proxy.getProxyClass(Proxy.java:398) ~[callee-service:na]
at org.springframework.util.ClassUtils.createCompositeInterface(ClassUtils.java:783) ~[na:na]
at org.springframework.aop.aspectj.AspectJExpressionPointcut.getTargetShadowMatch(AspectJExpressionPointcut.java:432) ~[na:na]
at org.springframework.aop.aspectj.AspectJExpressionPointcut.matches(AspectJExpressionPointcut.java:290) ~[na:na]
at org.springframework.aop.support.AopUtils.canApply(AopUtils.java:251) ~[na:na]
at org.springframework.aop.support.AopUtils.canApply(AopUtils.java:288) ~[na:na]
at org.springframework.aop.support.AopUtils.findAdvisorsThatCanApply(AopUtils.java:320) ~[na:na]
at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findAdvisorsThatCanApply(AbstractAdvisorAutoProxyCreator.java:128) ~[callee-service:6.0.3]
at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findEligibleAdvisors(AbstractAdvisorAutoProxyCreator.java:97) ~[callee-service:6.0.3]
at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean(AbstractAdvisorAutoProxyCreator.java:78) ~[callee-service:6.0.3]
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:366) ~[callee-service:6.0.3]
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:318) ~[callee-service:6.0.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:435) ~[callee-service:6.0.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1754) ~[callee-service:6.0.3]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599) ~[callee-service:6.0.3]
... 16 common frames omitted
So I began to add more proxy hints, but at some point I stopped because even with that hint configuration, that's not enough to make it work.
hints.proxies()
.registerJdkProxy(OrderedFilter.class, Filter.class, BeanNameAware.class, EnvironmentAware.class,
EnvironmentCapable.class, ServletContextAware.class, InitializingBean.class, DisposableBean.class)
.registerJdkProxy(WebServerFactoryCustomizer.class, Ordered.class)
.registerJdkProxy(ApplicationContextAware.class, EnvironmentCapable.class, EnvironmentAware.class,
Servlet.class, ServletConfig.class, Serializable.class)
.registerJdkProxy(DispatcherServletPath.class, ServletContextInitializer.class, Ordered.class)
.registerJdkProxy(ErrorPageRegistrar.class, Ordered.class)
.registerJdkProxy(ViewResolver.class, Ordered.class, ServletContextAware.class, ApplicationContextAware.class)
.registerJdkProxy(ErrorViewResolver.class, Ordered.class)
.registerJdkProxy(ErrorAttributes.class, Ordered.class)
.registerJdkProxy(ErrorAttributes.class, HandlerExceptionResolver.class, Ordered.class)
.registerJdkProxy(WebMvcConfigurer.class, ServletContextAware.class)
.registerJdkProxy(ResourceLoaderAware.class, ApplicationContextAware.class, ServletContextAware.class)
.registerJdkProxy(ConfigurableTomcatWebServerFactory.class, ResourceLoaderAware.class,
ConfigurableServletWebServerFactory.class, ConfigurableWebServerFactory.class);
@jhoeller It looks like AspectJExpressionPointcut#getTargetShadowMatch
tentatively create proxies for every bean in this try catch block. I am wondering about the performance impact on the JVM, and on native that means we would need to create proxy hints for every bean, leading to massive compatibility headaches and increased footprint. Is this something we could refine from your point of view?
That composite interface creation is only really needed for default methods in interfaces and similar scenarios where interface-based method implementations are inherited rather than implemented locally. I've added a corresponding condition to reduce the creation of composite interfaces to actual method references in the current pointcut, that is, only when the pointcut actually refers to a non-overridden default method or the like. This should eliminate all of the common cases above.
I confirm the example is now working as expected with method hints (hints.reflection().registerType(TestAspect.class, MemberCategory.INVOKE_DECLARED_METHODS);
) which will be inferred via #28711.
@sdeleuze thank you for finally having this fixed is there any kind of way i can try it out myself already ?
boot 3.0.2-snapshot still seems to rely on framework-core 6.0.3 and including framework-core 6.0.4-SNAPSHOT did not solve the issue
or do i just have to wait for a release ? :)
thx
Thanks should go to @jhoeller for the fix ;-)
You can override the Framework version in Boot, otherwise just wait a bit, Spring Framework 6.0.4 has been released today and will be picked up by Boot shortly.
yes i saw it on twitter yesterday evening, thx 4 letting me know and of course also to @jhoeller
@sdeleuze I can happily report that everything is working now with boot 3.0.2-SNAPSHOT Even more complex applications finally including CircuitBreaker Annotation So thx again to both of you.
As a sugar on top .. would it be possible to get rid of the "hints.reflection().registerType" ? Because basically everything annotated with Aspect should qualify already qualify for reflection ?
Or .. "at least" as a fallback, at least have a working variant of "RegisterForReflectionBinding"
Then it would also be nice to have spring cloud resilience4j working out of the box. I opened an issue a while ago, that was just closed https://github.com/spring-cloud/spring-cloud-circuitbreaker/issues/159
And this basically needs " hints.reflection().registerType(io.github.resilience4j.spring6.circuitbreaker.configure.CircuitBreakerAspect.class, builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));"
Please create a related issue with me mentioned with a minimal reproducer and a detailed description of the type hint inference you would expect.