@Async doesn't work well with AopContext

First, let's see a example below:

the demo's link:https://github.com/lixiaolong11000/async

class AsynApplicationTests {

    @Test
    void contextLoads() {
    }


    @Test
    public void asynExposeProxy() throws InterruptedException {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfigure.class);
        AsyncTransactionalTestBean asyncTransactionalTestBean = ctx.getBean(AsyncTransactionalTestBean.class);
        AsyncTestBean asyncTestBean = ctx.getBean(AsyncTestBean.class);

        assertThat(AopUtils.isAopProxy(asyncTransactionalTestBean)).as("asyncTransactionalTestBean is not a proxy").isTrue();
        assertThat(AopUtils.isAopProxy(asyncTestBean)).as("asyncTestBean is not a proxy").isTrue();

        asyncTransactionalTestBean.testTransToAsync();

        asyncTransactionalTestBean.testAsyncToAsync();

        asyncTestBean.testAsyncToAsyncTOAsync();

        ctx.close();
    }


    @Service
    public static class AsyncTransactionalTestBean {

        @Transactional
        public Collection<?> testTransToAsync() {
            System.out.println("testTransToAsync " + Thread.currentThread().getName());
            ((AsyncTransactionalTestBean) AopContext.currentProxy()).testAsyncToAsync();
            return null;
        }


        @Async
        public void testAsyncToAsync() {
            System.out.println("testAsyncToAsync " + Thread.currentThread().getName());
            ((AsyncTransactionalTestBean) AopContext.currentProxy()).testAsync();
        }

        @Async
        public void testAsync() {
            System.out.println("testAsync " + Thread.currentThread().getName());
        }
    }


    @Service
    public static class AsyncTestBean {

        @Async
        public Collection<?> testAsyncToAsyncTOAsync() {
            System.out.println("testAsyncToAsyncTOAsync " + Thread.currentThread().getName());
            ((AsyncTransactionalTestBean) AopContext.currentProxy()).testAsyncToAsync();
            return null;
        }

        @Async
        public void testAsyncToAsync() {
            System.out.println("testAsyncToAsync " + Thread.currentThread().getName());
            ((AsyncTransactionalTestBean) AopContext.currentProxy()).testAsync();
        }

        @Async
        public void testAsync() {
            System.out.println("testAsync " + Thread.currentThread().getName());
        }
    }


    @EnableAsync
    @EnableAspectJAutoProxy(exposeProxy = true)
    @Configuration
    @EnableTransactionManagement
    static class AppConfigure  {

        @Bean
        public AsyncTransactionalTestBean asyncTransactionalTestBean() {
            return new AsyncTransactionalTestBean();
        }

        @Bean
        public AsyncTestBean asyncTestBean() {
            return new AsyncTestBean();
        }

        @Bean
        public PlatformTransactionManager txManager() {
            return new MockTransactionManager();
        }
    }
}

run AsynApplicationTests.asynExposeProxy() we can find the exception:

java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.
    at org.springframework.aop.framework.AopContext.currentProxy(AopContext.java:69) ~[spring-aop-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at com.example.asyn.AsynApplicationTests$AsyncTransactionalTestBean.testAsyncToAsync(AsynApplicationTests.java:64) ~[test/:na]
    at com.example.asyn.AsynApplicationTests$AsyncTransactionalTestBean$$FastClassBySpringCGLIB$$adeba3a5.invoke(<generated>) ~[test/:na]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:769) ~[spring-aop-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747) ~[spring-aop-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115) ~[spring-aop-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_181]
    at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_181]

Second, I find the reason is : @Async doesn't work well with AopContext. because:

  • @EnableAspectJAutoProxy(exposeProxy = true) can make AopContext work in current Thread,not work in another Thread. so the test above asyncTransactionalTestBean.testTransToAsync() result in Exception
  • the test above asyncTransactionalTestBean.testAsyncToAsync() result in Excepton because AsyncAnnotationBeanPostProcessor、AsyncExecutionInterceptor、AsyncAnnotationAdvisor not surport AopContext.

Third, summary

  • When Aop(cglib,jdkproxy) use AopContext,@Async doesn't work well
  • @Aysnc is also a Aop, but it not suport AopContext

Final, I want contribute to spring to enhance it, thanks

Comment From: jhoeller

On review, we see AopContext as a special-purpose feature for circular call arrangements in certain AOP scenarios, so really not meant to be broadly applied. In particular for asynchronous execution, we do not intend to support this at all, and we might even deprecate exposeProxy and AopContext for regular execution at some point.

Thanks for the PR, in any case!