Search code examples
javaspringaopdynamic-proxy

How can I (or should I) apply Aspect Advice to a dynamic proxy?


Say I have Spring AOP configured like

@Configuration
@EnableAspectJAutoProxy
@ComponentScan
class Config { ... }

and I have some interface, ProxiableInterface. I implement it with

ProxiableInterface pi = (ProxiableInterface) Proxy.newProxyInstance(
    applicationContext.getClassLoader(),
    new Class[] {ProxiableInterface.class},
    (proxy, method, args) -> { ... });

I also have an Aspect:

@Aspect
class SomeAspect {
    @Around("execution(package.ProxiableInterface.*)")
    Object doStuffAround(ProceedingJoinPoint pjp) { ... }
}

When I call a method on ProxiableInterface, the Aspect method does not get called. Is there a way to sort of "register" this Proxy so that it becomes advised? I could simply do what the advice does inside the Proxy's InvocationHandler, but it would result in code duplication since this Advice already applies to other areas of the code as well.


Solution

  • The solution I found is to use org.springframework.aop.aspectj.annotation.AspectJProxyFactory.

    An added complication in my case is that the class I want to proxy is abstract, not an interface, meaning Spring must proxy it with CGLIB and not a JDK proxy. However, this proved in a way to be more useful than it is annoying.

    First, I have an abstract class:

    public abstract class Something {
    
        abstract void doWhatever();
    
        @Override
        public final boolean equals(Object o) {
            return this == o;
        }
    
        @Override
        public final int hashCode() {
            return System.identityHashCode(this);
        }
    
        @Override
        public final String toString() {
            return getClass().getName() + " " + hashCode();
        }
    }
    

    (I've overridden some Object methods and made them final because they would otherwise be proxied, but in that case I would need to write Advice for them.)

    Then I have an AspectJProxyFactory and a bean with prototype scope as such:

    @Configuration
    @EnableAspectJAutoProxy
    class Config {
    
        private static final AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
        static {
            proxyFactory.setTargetClass(Something.class);
            proxyFactory.addAspect(SomeAspect.class);
        }
    
        @Bean
        @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
        Something something() {
    
            return proxyFactory.getProxy();
        }
    }
    

    Note that the Somethings must be beans for Spring AOP to be made aware of them. And this is the Aspect:

    @Aspect
    public class SomeAspect {
    
        @Around("execution(void org.luc.Something.doWhatever())")
        void around(ProceedingJoinPoint pjp) throws Throwable {
    
            System.out.println(pjp.getThis().toString() + ": I've been advised");
        }
    }
    

    This way I can get distinct instances from the proxy factory, which will have been advised by the AOP auto-proxy.

        Something something1 = applicationContext.getBean(Something.class);
        Something something2 = applicationContext.getBean(Something.class);
    
        Assert.assertEquals(something1, something1);
        Assert.assertNotEquals(something1, something2);
    
        something1.doWhatever();
        something2.doWhatever();
    

    This prints something like:

    org.luc.Something$$EnhancerBySpringCGLIB$$cadae9a8 638486177: I've been advised org.luc.Something$$EnhancerBySpringCGLIB$$cadae9a8 426019904: I've been advised