spring-projects/spring-framework

Infer hints required for aspects

mhalbritter opened this issue · 10 comments

In the spring-native sample class-proxies-aop in the branch sb-3.0.x, the @Aspect works when running in AOT mode, but not in a native-image.
There's no exception, the aspect just doesn't get executed.

Likely depends on #28115.

The proxy is now created properly but the aspect smoke test still fails.

I was able to make it work with:

static class AspectRuntimeHints implements RuntimeHintsRegistrar {
		@Override
		public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
			hints.reflection().registerType(TestAspect.class,
					builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));
			hints.proxies().registerJdkProxy(FactoryBean.class, BeanClassLoaderAware.class, ApplicationListener.class);
			hints.proxies().registerJdkProxy(ApplicationAvailability.class, ApplicationListener.class);
		}
	}

Reflection hint should probably be inferred on Spring Framework side.

The 2 proxies are required by org.springframework.boot.availability.ApplicationAvailabilityBean and org.springframework.boot.admin.SpringApplicationAdminMXBeanRegistrar and are not created automatically by my #28980 local fix (@jhoeller could you please confirm that's expected?). Related stacktrace is:

at java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:48) ~[aspect:na]
	at java.lang.reflect.Proxy.getProxyClass(Proxy.java:398) ~[aspect:na]
	at org.springframework.util.ClassUtils.createCompositeInterface(ClassUtils.java:784) ~[na:na]
	at org.springframework.aop.aspectj.AspectJExpressionPointcut.getTargetShadowMatch(AspectJExpressionPointcut.java:437) ~[na:na]
	at org.springframework.aop.aspectj.AspectJExpressionPointcut.matches(AspectJExpressionPointcut.java:295) ~[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) ~[aspect:6.0.0-SNAPSHOT]
	at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findEligibleAdvisors(AbstractAdvisorAutoProxyCreator.java:97) ~[aspect:6.0.0-SNAPSHOT]
	at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean(AbstractAdvisorAutoProxyCreator.java:78) ~[aspect:6.0.0-SNAPSHOT]
	at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:366) ~[aspect:6.0.0-SNAPSHOT]
	at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:318) ~[aspect:6.0.0-SNAPSHOT]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:435) ~[aspect:6.0.0-SNAPSHOT]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1757) ~[aspect:6.0.0-SNAPSHOT]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599) ~[aspect:6.0.0-SNAPSHOT]

If it is confirmed those can't be inferred, I guess those proxies hints should be contributed on Boot side

Looks like the aspect smoke test samples now work, but there are other issues preventing it to work in a reliable fashion for various use case. #29519 is likely one of the blockers.

We should also check if aspect annotations and the methods and classes where they are applied are available via reflection, I suspect #29765 sample is broken by annotation not accessible via reflection on native.

We could potentially reuse Spring AOP infrastructure to identify the reflection entries needed to make aspects working out of the box. We could create a dedicated BeanFactoryInitializationAotProcessor that would use BeanFactoryAspectJAdvisorsBuilder to get the List<Advisor> and process each of them. Maybe we could get the needed information for inference via instanceof PointcutAdvisor and instanceof AbstractAspectJAdvice checks.

Sorry for moving that again, but I won't have the bandwidth to tackle that in Spring 6.0 timeframe, so let's target 6.1.

This fix has been tested successfully with a few samples (including spring-aot-smoke-test/framework/aspect one).
#30529 repro is still broken for reasons that remain to be identified, but this will be handle via the dedicated issue.

Feedback welcome.

I believe this broke the batch AOT sample as follows:

Exception in thread "main" java.lang.NoClassDefFoundError: org/aspectj/lang/annotation/Pointcut
        at org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactory.<clinit>(AbstractAspectJAdvisorFactory.java:61)
        at org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder.<init>(BeanFactoryAspectJAdvisorsBuilder.java:60)
        at org.springframework.aop.aspectj.annotation.AspectJBeanFactoryInitializationAotProcessor.processAheadOfTime(AspectJBeanFactoryInitializationAotProcessor.java:44)
        at org.springframework.context.aot.BeanFactoryInitializationAotContributions.getContributions(BeanFactoryInitializationAotContributions.java:67)
        at org.springframework.context.aot.BeanFactoryInitializationAotContributions.<init>(BeanFactoryInitializationAotContributions.java:49)
        at org.springframework.context.aot.BeanFactoryInitializationAotContributions.<init>(BeanFactoryInitializationAotContributions.java:44)
        at org.springframework.context.aot.ApplicationContextAotGenerator.lambda$processAheadOfTime$0(ApplicationContextAotGenerator.java:58)
        at org.springframework.context.aot.ApplicationContextAotGenerator.withCglibClassHandler(ApplicationContextAotGenerator.java:67)
        at org.springframework.context.aot.ApplicationContextAotGenerator.processAheadOfTime(ApplicationContextAotGenerator.java:53)
        at org.springframework.context.aot.ContextAotProcessor.performAotProcessing(ContextAotProcessor.java:106)
        at org.springframework.context.aot.ContextAotProcessor.doProcess(ContextAotProcessor.java:84)
        at org.springframework.context.aot.ContextAotProcessor.doProcess(ContextAotProcessor.java:49)
        at org.springframework.context.aot.AbstractAotProcessor.process(AbstractAotProcessor.java:82)
        at org.springframework.boot.SpringApplicationAotProcessor.main(SpringApplicationAotProcessor.java:80)
Caused by: java.lang.ClassNotFoundException: org.aspectj.lang.annotation.Pointcut
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
        ... 14 more

The batch sample is failing. I don't know if it is special or if something else is involved.

Fixed by 74155e3