spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.46k stars 38.09k forks source link

Aspect executed twice - @AfterThrowing #33704

Open aviv-amdocs opened 6 days ago

aviv-amdocs commented 6 days ago

Affects: 6.1.7-6.1.13


This might be a special case of #32970, except it's not fixed yet. We have both aspectj-maven-plugin and @Aspect.

Demo code (short):

We have this pattern where we use an Aspect interceptor to replace exceptions thrown by some library with application-specific exceptions. This worked fine until Spring Framework 6.1.7, and since then, the interceptor is triggered (weaved?) twice. The interceptor doesn't expect the exception it's provided, so it throws an exception nobody expects (In the demo, it just wraps the exception with a new one, and the test checks for getCause().getMessage()).

jhoeller commented 6 days ago

The ajc-compiled check is only reliable for aspect classes, not for target classes being weaved. We'll try to revisit this but I'm not sure this will ever be 100% reliable since we are checking for internal ajc markers there. Recent AspectJ recommendations for reusable aspects do not differentiate between the usage model anymore, suggesting ajc-pre-compiled aspects to be usable everywhere, which triggered our change where Spring AOP does not exclude ajc-compiled aspects for proxying anymore.

Conceptually, the root of this problem is the exposure of the aspect class as a Spring bean. With Spring auto-proxying being active (which it is by default in Boot), Spring AOP will by design consider any such aspect bean for regular AOP proxying. If an aspect is exclusively meant to be used with ajc compilation (as performed by the AspectJ Maven plugin), you either need to turn off Boot's auto-proxying auto-configuration - or not expose the AspectJ-managed aspect as a Spring bean to begin with.

With ajc, an aspect is an ajc-managed singleton instance anyway, not a real Spring bean with a Spring-managed lifecycle. So I assume that the only reason for bean exposure is autowiring your interceptor. Consider revising your interceptor setup along the following lines, with the interceptor setting itself into the ajc-managed aspect instance, and no @Bean AspectInjector method at all anymore. This avoids the mixture of models and lifecycles and will work reliably with any Spring version:

    @Bean
    public Interceptor interceptor() {
        Interceptor interceptor = new Interceptor();
    Aspects.aspectOf(AspectInjector.class).interceptor = interceptor;
    return interceptor;
    }