透过现象看原理——详解Spring中Bean的this调用导致AOP失效的原因(上)

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 透过现象看原理——详解Spring中Bean的this调用导致AOP失效的原因(上)

作者:光闪
my.oschina.net/guangshan/blog/1807721

my.oschina.net/guangshan/blog/1807721

推荐:

前言

在我们使用Spring时,可能有前辈教导过我们,在bean中不要使用this来调用被@Async、@Transactional、@Cacheable等注解标注的方法,this下注解是不生效的。

那么大家可曾想过以下问题

  • 为何致this调用的方法,注解会不生效
  • 这些注解生效的原理又是什么
  • 如果确实需要调用本类方法,且还需要注解生效,该怎么做?
  • 代理是否可以做到this调用注解就直接生效?
  • 通过本文,上面的疑问都可以解决,而且可以学到很多相关原理知识,信息量较大,那么就开始吧

    现象

    以@Async注解为例,@Async注解标记的方法,在执行时会被AOP处理为异步调用,调用此方法处直接返回,@Async标注的方法使用其他线程执行。

    使用Spring Boot驱动

    
    @SpringBootApplication
    @EnableAsync
    public class Starter {
    
        public static void main(String[] args) {
            SpringApplication.run(Starter.class, args);
        }
    }
    
    @Component
    public class AsyncService {
    
        public void async1() {
            System.out.println("1:" + Thread.currentThread().getName());
            this.async2();
        }
    
        @Async
        public void async2() {
            System.out.println("2:" + Thread.currentThread().getName());
        }
    }
    
    @RunWith(SpringRunner.class) 
    @SpringBootTest(classes = Starter.class)
    public class BaseTest {
    
        @Autowired
        AsyncService asyncService;
    
        @Test
        public void testAsync() {
            asyncService.async1();
            asyncService.async2();
        }
    
    }
    

    输出内容为:

    1:main 2:main 2:SimpleAsyncTaskExecutor-2

    第一行第二行对应async1()方法,第三行对应async2()方法,可以看到直接使用asyncService.async2()调用时使用的线程为SimpleAsyncTaskExecutor,而在async1()方法中使用this调用,结果却是主线程,原调用线程一致。这说明@Async在this调用时没有生效。

    思考&猜测

    已知对于AOP动态代理,非接口的类使用的是基于CGLIB的动态代理,而CGLIB的动态代理,是基于现有类创建一个子类,并实例化子类对象。在调用动态代理对象方法时,都是先调用子类方法,子类方法中使用方法增强Advice或者拦截器MethodInterceptor处理子类方法调用后,选择性的决定是否执行父类方法。

    那么假设在调用async1方法时,使用的是动态生成的子类的实例,那么this其实是基于动态代理的子类实例对象,this调用是可以被Advice或者MethodInterceptor等处理逻辑拦截的,那么为何理论和实际不同呢?

    这里大胆推测一下,其实async1方法中的this不是动态代理的子类对象,而是原始的对象,故this调用无法通过动态代理来增强。

    关于上面AOP动态代理使用CGLIB相关的只是,可以参考:https://my.oschina.net/guangshan/blog/1797461

    下面开始详细分析。

    源码调试分析原理

    首先要弄清楚@Async是如何生效的:

    1.分析Async相关组件

    从生效入口开始看,@EnableAsync注解上标注了@Import(AsyncConfigurationSelector.class)

    @Import的作用是把后面的@Configuration类、ImportSelector类或者ImportBeanDefinitionRegistrar类中import的内容自动注册到ApplicationContext中。关于这三种可Import的类,这里先不详细说明,有兴趣的读者可以自行去Spring官网查看文档或者等待我的后续文章。

    这里导入了AsyncConfigurationSelector,而AsyncConfigurationSelector在默认情况下,会选择出来ProxyAsyncConfiguration类进行导入,即把ProxyAsyncConfiguration类作为@Configuration类配置到ApplicationContext中。那么这里的关键就是ProxyAsyncConfiguration类,看代码

    
    @Configuration
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
    
        @Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
        @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
        public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
            Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");
            AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
            Class? extends Annotation customAsyncAnnotation = this.enableAsync.getClass("annotation");
            if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {
                bpp.setAsyncAnnotationType(customAsyncAnnotation);
            }
            if (this.executor != null) {
                bpp.setExecutor(this.executor);
            }
            if (this.exceptionHandler != null) {
                bpp.setExceptionHandler(this.exceptionHandler);
            }
            bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
            bpp.setOrder(this.enableAsync.IntegergetNumber("order"));
            return bpp;
        }
    
    }
    

    这段代码的作用是把AsyncAnnotationBeanPostProcessor作为Bean注册到Context中。那么核心就是把AsyncAnnotationBeanPostProcessor这个BeanPostProcessor,也就是Spring大名鼎鼎的BPP。

    在一个Bean实例生成后,会交给BPP的postProcessBeforeInitialization方法进行加工,此时可以返回与此Bean相兼容的其他Bean实例,例如最常见的就是在这里返回原对象的动态代理对象。

    在这个方法执行后,会调用Bean实例的init相关方法。调用的方法是InitializingBean接口的afterPropertiesSet方法,以及@Bean声明中initMethod指定的初始化方法。

    在调用init方法之后,会调用BPP的postProcessAfterInitialization方法进行后置处理。此时处理同postProcessBeforeInitialization,也可以替换原bean的实例。

    我们看下这个Async相关的BPP做了什么操作:

    
    // 潜质处理不做任何动作,可保证在调用bean的init之前,bean本身没有任何变化。
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean;
    }
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        // 如果是AOP相关的基础组件bean,如ProxyProcessorSupport类及其子类,则直接返回。
        if (bean instanceof AopInfrastructureBean) {
            // Ignore AOP infrastructure such as scoped proxies.
            return bean;
        }
    
        if (bean instanceof Advised) {
            // 如果已经是Advised的,即已经是被动态代理的实例,则直接添加advisor。
            Advised advised = (Advised) bean;
            if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
                // 如果没有被frozen(即冷冻,不再做改动的动态代理实例)且是Eligbile(合适的),则把其添加到advisor中。根据配置决定插入位置。
                // Add our local Advisor to the existing proxy's Advisor chain...
                if (this.beforeExistingAdvisors) {
                    advised.addAdvisor(0, this.advisor);
                }
                else {
                    advised.addAdvisor(this.advisor);
                }
                return bean;
            }
        }
    
        if (isEligible(bean, beanName)) {
            // 如果是Eligible合适的,且还不是被代理的类,则创建一个代理类的实例并返回。
            ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
            if (!proxyFactory.isProxyTargetClass()) {
                evaluateProxyInterfaces(bean.getClass(), proxyFactory);
            }
            proxyFactory.addAdvisor(this.advisor);
            customizeProxyFactory(proxyFactory);
            return proxyFactory.getProxy(getProxyClassLoader());
        }
    
        // No async proxy needed.
        return bean;
    }
    // 准备ProxyFactory对象
    protected ProxyFactory prepareProxyFactory(Object bean, String beanName) {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.copyFrom(this);
        // 设置被代理的bean为target,这个bean是真实的bean。
        proxyFactory.setTarget(bean);
        return proxyFactory;
    }
    

    Spring在对一个类进行AOP代理后,会为此类加上Advised接口,返回的动态代理对象都会带上Advised接口修饰,那么第一段逻辑判断bean instanceof Advised的目的就是判断是否已经是被动态代理的类,如果是,则为其添加一个Advisor增强器。

    如果不是动态代理的对象,因为@Async要为方法增加代理,并转换为异步执行,故需要把原始bean转换为被AOP动态代理的bean。也就是下面的逻辑。

    有关上面这一段代码创建动态代理的详细原理,请参考:https://my.oschina.net/guangshan/blog/1797461

    关于@Async再多提一点:上面注册进去的advisor类型是AsyncAnnotationAdvisor。其中包括了PointCut,类型是AnnotationMatchingPointcut,指定了只有@Async标记的方法或者类此AOP增强器才生效。还有一个Advice,用于增强@Async标记的方法,转换为异步,类型是AnnotationAsyncExecutionInterceptor,其中的invoke方法是真正调用真实方法的地方,大家有兴趣可以仔细研究其中的内容,这样就能摸清楚@Async方法的真实执行逻辑了。

    相关组件上面都已经提及并进行了简单的分析,现在我们进入下一阶段,通过真正的执行逻辑来分析this调用不生效的原因。

    2.深入真实调用逻辑

    @Async大多数都是标记的类中的方法,故AOP的实现也多是基于CGLIB的,下面以CGLIB动态代理为例分析真实调用逻辑。

    通过完全读懂Spring框架之AOP实现原理这篇文章,可以得知,一个基于CGLIB的AOP动态代理bean,真实的执行逻辑是在DynamicAdvisedInterceptor中:

    
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object oldProxy = null;
        boolean setProxyContext = false;
        Class? targetClass = null;
        Object target = null;
        try {
            if (this.advised.exposeProxy) {
                // 需要则暴露
                // Make invocation available if necessary.
                oldProxy = AopContext.setCurrentProxy(proxy);
                setProxyContext = true;
            }
            // May be null. Get as late as possible to minimize the time we
            // "own" the target, in case it comes from a pool...
            // 重点:获取被代理的目标对象
            target = getTarget();
            if (target != null) {
                targetClass = target.getClass();
            }
            // 获取拦截器链
            ListObject chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
            Object retVal;
            // Check whether we only have one InvokerInterceptor: that is,
            // no real advice, but just reflective invocation of the target.
            if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
                // We can skip creating a MethodInvocation: just invoke the target directly.
                // Note that the final invoker must be an InvokerInterceptor, so we know
                // it does nothing but a reflective operation on the target, and no hot
                // swapping or fancy proxying.
                // 如果链是空且是public方法,则直接调用
                Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                retVal = methodProxy.invoke(target, argsToUse);
            }
            else {
                // We need to create a method invocation...
                // 否则创建一个CglibMethodInvocation以便驱动拦截器链
                retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
            }
            // 处理返回值,同JDK动态代理
            retVal = processReturnType(proxy, target, method, retVal);
            return retVal;
        }
        finally {
            if (target != null) {
                releaseTarget(target);
            }
            if (setProxyContext) {
                // Restore old proxy.
                AopContext.setCurrentProxy(oldProxy);
            }
        }
    }
    

    注意上面真实调用的部分,在没有advisor的情况下,使用的其实是:

    
    methodProxy.invoke(target, argsToUse)
    

    在有代理的情况下,使用的是:

    
    new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
    

    而在CglibMethodInvocation中,检查到调用链执行完之后,会调用真实的方法:invokeJoinpoint。在CglibMethodInvocation中,该方法的实现是

    
    // CglibMethodInvocation中的实现
    protected Object invokeJoinpoint() throws Throwable {
        if (this.publicMethod) {
            return this.methodProxy.invoke(this.target, this.arguments);
        }
        else {
            return super.invokeJoinpoint();
        }
    }
    // 父类实现是
    protected Object invokeJoinpoint() throws Throwable {
        return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
    }
    

    可以看到调用方法时,传入的实例都是target,这个target是从DynamicAdvisedInterceptor的getTarget方法中获得的,代码如下

    
    protected Object getTarget() throws Exception {
        return this.advised.getTargetSource().getTarget();
    }
    

    而这个advised的target则是在ProxyFactory的实例方法中设置的:proxyFactory.setTarget(bean);

    也就是说这个target其实是真实的被代理的bean。

    通过上面的分析,我们可以得到结论,在一个被动态代理的对象,在执行完AOP所有的增强逻辑之后,最终都会使用被代理对象作为实例调用真实的方法,即相当于调用了:target.method()方法。由此得出结论,在target.method()方法中,this引用必然是target自身,而不是生成的动态代理对象实例。

    补充一下,Spring在创建一个Bean之后,对其包装并生成动态代理对象都是后置的举动,故会先生成真实类的实例bean,再动态创建动态代理bean,在动态代理bean中,会持有真实的bean的实例。

    就拿最上面的@Async代码实例举例,我们可以看到this其实是AsyncService的原始实例,而不是代理对象

    实例:

    透过现象看原理:详解Spring中Bean的this调用导致AOP失效的原因(上)

    总结: 

    因为AOP动态代理的方法真实调用,会使用真实被代理对象实例进行方法调用,故在实例方法中通过this获取的都是被代理的真实对象的实例,而不是代理对象自身。

    3.解决this调用的几个替代方法

    既然已知原因,那么解决的方法就有定向了,核心就是如何获得动态代理对象,而不是使用this去调用。

    提供以下几种方法:

    1.通过ApplicationContext来获得动态代理对象

    
    @Component
    public class AsyncService implements ApplicationContextAware {
    
        private ApplicationContext applicationContext;
    
        public void async1() {
            System.out.println("1:" + Thread.currentThread().getName());
            // 使用AppicationContext来获得动态代理的bean
            this.applicationContext.getBean(AsyncService.class).async2();
        }
    
        @Async
        public void async2() {
            System.out.println("2:" + Thread.currentThread().getName());
        }
    
        // 注入ApplicationContext
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    }
    

    执行结果是:

    1:main 2:SimpleAsyncTaskExecutor-2 2:SimpleAsyncTaskExecutor-3

    可以看到完美达到了我们的目的。同理是用BeanFactoryAware可达到同样的效果。

    未完…..

    点击图片加入Spring交流群

    ↓↓↓

    看完本文有收获?请转发分享给更多人

    透过现象看原理:详解Spring中Bean的this调用导致AOP失效的原因(上)
    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> 透过现象看原理——详解Spring中Bean的this调用导致AOP失效的原因(上)


     上一篇
    Spring基础系列-AOP源码分析 Spring基础系列-AOP源码分析
    一、概述 Spring的两大特性:IOC和AOP。 AOP是面向切面编程,Spring内置了自己实现的基于动态代理技术的AOP,同时还支持成熟的AspectJ框架,我们这里主要讲述的还是内置的基于动态代理的AOP实现。因为面对一些普通的需求
    2021-04-05
    下一篇 
    透过现象看原理——详解Spring中Bean的this调用导致AOP失效的原因(下) 透过现象看原理——详解Spring中Bean的this调用导致AOP失效的原因(下)
    (续上文)通过AopContext获取动态代理对象 @Component public class AsyncService { public void async1() { System.out.println(
    2021-04-05