透过现象看原理——详解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失效的原因(下)

(续上文)

通过AopContext获取动态代理对象


@Component
public class AsyncService {

    public void async1() {
        System.out.println("1:" + Thread.currentThread().getName());
        ((AsyncService) AopContext.currentProxy()).async2();
    }

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

}

这种做法非常简洁,但是在默认情况下是不起作用的! 因为AopContext中拿不到currentProxy,会报空指针。

通过上面的动态代理执行源码的地方可以看到逻辑:


if (this.advised.exposeProxy) {
    // Make invocation available if necessary.
    oldProxy = AopContext.setCurrentProxy(proxy);
    setProxyContext = true;
}

而在ProxyConfig类中,有如下注释用来说明exposeProxy的作用,就是用于在方法中获取动态代理的对象的。


/**
 * Set whether the proxy should be exposed by the AOP framework as a
 * ThreadLocal for retrieval via the AopContext class. This is useful
 * if an advised object needs to call another advised method on itself.
 * (If it uses {@code this}, the invocation will not be advised).
 * pDefault is "false", in order to avoid unnecessary extra interception.
 * This means that no guarantees are provided that AopContext access will
 * work consistently within any method of the advised object.
 */
public void setExposeProxy(boolean exposeProxy) {
    this.exposeProxy = exposeProxy;
}

即只有exposeProxy为true时,才会把proxy动态代理对象设置到AopContext上下文中,这个配置默认是false。那么这个配置怎么修改呢?

在xml时代,我们可以通过配置:


aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/  

来修改全局的暴露逻辑。

在基于注解的配置中,我们需要使用


@EnableAspectJAutoProxy(proxyTargteClass = true, exposeProxy = true)

来配置。

遗憾的是,对于@Async,如此配置下依然不能生效。因为@Async使用的不是AspectJ的自动代理,而是使用代码中固定的创建代理方式进行代理创建的。

如果是@Transactional事务注解的话, 则是生效的。具体生效机制是通过@EnableTransactionManagement注解中的TransactionManagementConfigurationSelector类声明,其中声明导入了AutoProxyRegistrar类,该类获取注解中proxy相关注解配置,并根据配置情况,在BeanDefinition中注册一个可用于自动生成代理对象的AutoProxyCreator:


AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
    return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
}

而在@EnableAspectJAutoProxy注解中,@Import的AspectJAutoProxyRegistrar类又把这个BeanDefinition修改了类,同时修改了其中的exposeProxy属性。


AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
    return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
}
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
    return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

后面替换掉了前面的AutoProxyCreator,替换逻辑是使用优先级替换,优先级分别为:


APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);
APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);
APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);

这个逻辑都在registerOrEscalateApcAsRequired中,读者可以自己再看一下。

因为@Transactional注解和AspectJ相关注解的生成动态代理类都是使用的同一个Bean即上面的AutoProxyCreator处理的,该bean的name是org.springframework.aop.config.internalAutoProxyCreator,他们公用相同的属性,故对于@Transactional来说,@EnableAspectJAutoProxy的属性exposeProxy=true也是生效的。但是@Async的注解生成的代理类并不是通过这个autoProxyCreator来生成的,故不能享受到上面的配置。

3.基于上面的源码,我们可以得到第三种处理方法

在某个切入时机,手动执行AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);静态方法,当然前提是有一个BeanDefinitionRegistry,且时机要在BeanDefinition已经创建且动态代理对象还没有生成时调用。

使用这种方式,无需使用@EnableAspectJAutoProxy即可。

这种方式同样不适用于@Async,适用于@Transactional。

4.手动修改各种BeanPostProcessor的属性

以@Async为例,其通过AsyncAnnotationBeanPostProcessor来生成动态代理类,我们只要在合适时机即该BPP已创建,但是还未被使用时,修改其中的exposeProxy属性,使用AsyncAnnotationBeanPostProcessor.setExposeProxy(true)即可。

这种方式要针对性的设置特定的bean的exposeProxy属性true。适用于@Async,观察原理可以知道3和4其实核心都是相同的,就是设置AutoProxyCreater的exposed属性为true。AsyncAnnotationBeanPostProcessor其实也是一个AutoProxyCreater,他是ProxyProcessorSupport的子类。

对于@Async可以使用1、4方式,对于@Transactional则可以使用这四种任意方式。

欢迎大家补充其他方法。

4.是否可以做到this调用使动态代理生效

基于我们的推测,如果this引用是动态代理对象的话,则this调用其实是可以调用到父类的方法的,只要调用的是父类方法,那么在父类重写的方法中加入的动态代理拦截就是可以生效的。此种场景在Spring中是否存在呢?答案是肯定的,就在Spring提供的@Configuration配置类中,就有这种场景的应用,下面见示例:


@Configuration
public class TestConfig {

    @Bean
    public Config config() {
        return new Config();
    }

    @Bean
    public ConfigOut configOut() {
        Config c1 = this.config();
        Config c2 = this.config();
        System.out.println(c1 == c2);
        ConfigOut configOut = new ConfigOut(this.config());
        return configOut;
    }

    public static class Config {}

    public static class ConfigOut {

        private Config config;

        private ConfigOut(Config config) {
            this.config = config;
        }

    }

}

在configOut方法中加入断点,调试观察c1与才 的值,也即this.config()返回的值,可以看到c1和c2是同一个对象引用,而不是每次调用方法都new一个新的对象。

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

那么这里是怎么做到this调用多次都返回同一个实例的呢?我们继续跟踪调试断点,查看整体的调用堆栈,发现这个方法configOut的调用处以及config方法的真实调用处是在ConfigurationClassEnhancer的内部类BeanMethodInterceptor中,为什么是这个方法呢?因为真实的Configuration类被动态替换为基于CGLIB创建的子类了。

而这个@Configuration类的处理,是基于ConfigurationClassPostProcessor这个BeanFactoryPostProcessor处理器来做的,在ConfigurationClassPostProcessor中的postProcessBeanDefinitionRegistry方法中,检查所有的bean,如果bean是被@Configuration、@Component、@ComponentScan、@Import、@ImportResource其中一个标注的,那么此类就会被视为Configuration类。在postProcessBeanDefinition方法中,会把@Configuration类动态代理为一个新类,使用CGLIB的enhancer来增强Configuration类。使用ConfigurationClassEnhancer的enhance方法处理为原有类的子类,参考代码:


/**
 * Loads the specified class and generates a CGLIB subclass of it equipped with
 * 加载特殊的Configuration类时,为其生成一个CGLIB的子类
 * container-aware callbacks capable of respecting scoping and other bean semantics.
 * 以便实现对@Bean方法的拦截或者增强
 * @return the enhanced subclass
 */
public Class? enhance(Class? configClass, ClassLoader classLoader) {
    if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
        // 如果已经是被增强的Configuration,则直接跳过
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Ignoring request to enhance %s as it has " +
                    "already been enhanced. This usually indicates that more than one " +
                    "ConfigurationClassPostProcessor has been registered (e.g. via " +
                    "context:annotation-config). This is harmless, but you may " +
                    "want check your configuration and remove one CCPP if possible",
                    configClass.getName()));
        }
        return configClass;
    }
    // 否则生成增强后的新的子类
    Class? enhancedClass = createClass(newEnhancer(configClass, classLoader));
    if (logger.isDebugEnabled()) {
        logger.debug(String.format("Successfully enhanced %s; enhanced class name is: %s",
                configClass.getName(), enhancedClass.getName()));
    }
    return enhancedClass;
}

/**
 * Creates a new CGLIB {@link Enhancer} instance.
 * 创建增强的CGLIB子类
 */
private Enhancer newEnhancer(Class? superclass, ClassLoader classLoader) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(superclass);
    // 增加接口以标记是被增强的子类,同时增加setBeanFactory方法,设置内部成员为BeanFactory。
    enhancer.setInterfaces(new Class?[] {EnhancedConfiguration.class});
    enhancer.setUseFactory(false);
    enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
    // BeanFactoryAwareGeneratorStrategy生成策略为生成的CGLIB类中添加成员变量$$beanFactory
    // 同时基于接口EnhancedConfiguration的父接口BeanFactoryAware中的setBeanFactory方法,设置此变量的值为当前Context中的beanFactory
    // 该BeanFactory的作用是在this调用时拦截该调用,并直接在beanFactory中获得目标bean。
    enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
    // 设置CALLBACK_FILTER,
    enhancer.setCallbackFilter(CALLBACK_FILTER);
    enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
    return enhancer;
}

// 增强时要使用的filters
// The callbacks to use. Note that these callbacks must be stateless.
private static final Callback[] CALLBACKS = new Callback[] {
        // 用于拦截@Bean方法的调用,并直接从BeanFactory中获取目标bean,而不是通过执行方法。
        new BeanMethodInterceptor(),
        // 用于拦截BeanFactoryAware接口中的setBeanFactory方法的嗲用,以便设置$$beanFactory的值。
        new BeanFactoryAwareMethodInterceptor(),
        // 不做任何操作
        NoOp.INSTANCE
};

/**
 * Uses enhancer to generate a subclass of superclass,
 * ensuring that callbacks are registered for the new subclass.
 * 设置callbacks到静态变量中,因为还没有实例化,所以只能放在静态变量中。
 */
private Class? createClass(Enhancer enhancer) {
    Class? subclass = enhancer.createClass();
    // Registering callbacks statically (as opposed to thread-local)
    // is critical for usage in an OSGi environment (SPR-5932)...
    Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
    return subclass;
}

可以看到这里的callbacks是注册到生成的子类的static中,这里只生成class而不实例化。

把此类设置到BeanDefinition中的beanClass属性中,在BeanDefinition初始化时会自动初始化子类。

上面的关键是CALLBACKS、CALLBACK_FILTER,分别代表增强器和增强器的过滤器。

关于Configuration类的CGLIB动态代理创建可以与SpringAOP体系创建的CGLIB动态代理做一个对比,区别是这里的动态代理的CALLBACKS和CALLBACK_FILTER。

这里我们以上面提到的BeanMethodInterceptor为例,来说明他的作用,以及this调用在这种情况下可以被动态代理拦截的原因。代码如下:


/**
 * enhancedConfigInstance: 被CGLIB增强的config类的实例,即CGLIB动态生成的子类的实例
 * beanMethod : @Bean标记的方法,即当前调用的方法,这个是通过CallbackFilter的accept方法筛选出来的,只可能是@Bean标注的方法。
 * beanMethodArgs : 方法调用的参数
 * cglibMethodProxy : cglib方法调用的代理,可以用来直接调用父类的真实方法。
*/
@Override
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
            MethodProxy cglibMethodProxy) throws Throwable {
    // 通过enhancedConfigInstance中cglib生成的成员变量$$beanFactory获得beanFactory。
    ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
    // 确认真实的beanName,用于在beanFactory中获得bean实例
    String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

    // Determine whether this bean is a scoped-proxy
    // 后面这个是确认是否是scoped作用域的bean,这里暂时不考虑,后续文章详细分析Scoped相关的逻辑和bean。
    Scope scope = AnnotatedElementUtils.findMergedAnnotation(beanMethod, Scope.class);
    if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
        String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
        if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
            beanName = scopedBeanName;
        }
    }

    // To handle the case of an inter-bean method reference, we must explicitly check the
    // container for already cached instances.
    // 拦截内部bean方法的调用,检查bean实例是否已经生成

    // First, check to see if the requested bean is a FactoryBean. If so, create a subclass
    // proxy that intercepts calls to getObject() and returns any cached bean instance.
    // This ensures that the semantics of calling a FactoryBean from within @Bean methods
    // is the same as that of referring to a FactoryBean within XML. See SPR-6602.
    // 检查是否是FactoryBean,当是FactoryBean时,即使是this调用也不能生成多次
    // 更特殊的,调用FactoryBean的getObject方法时,也不能生成多次新的Bean,否则取到的bean就是多个了,有违单例bean的场景。
    // 所以这里判断如果当前方法返回的bean,如果是FactoryBean的话,对FactoryBean进行代理
    // 代理的结果是拦截factoryBean实例的getObject方法,转化为通过BeanFactory的getBean方法来调用
    if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
            factoryContainsBean(beanFactory, beanName)) {
        // 上面加入BeanFactory.FACTORY_BEAN_PREFIX + beanName用来判断当前bean是否是一个FactoryBean。在BeanFactory中是通过FACTORY_BEAN_PREFIX前缀来区分当前要判断的目标类型的,
        // 如果是FACTORY_BEAN_PREFIX前缀的beanName,则获取之后会判断是否是FactoryBean,是则为true,否则为false。
        // 同时还判断了当前的Bean是否是在创建中,只有不是在创建中,才会返回true。第一个拿FactoryBean的name去判断,则肯定不在创建中。第二个的判断才是真正生效的可判断出是否在创建中的方法。
        Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
        // 只有不在创建中,才能调用BeanFactory去获取或者创建,否则会无限递归调用。
        // 上面的调用获取时,才会进行真正的初始化,实例化时还会再进一次这个方法,但是并不会执行到这个逻辑中,因为再进入时,会被标记为正在创建。真正的初始化时调用@Bean方法进行的,是在下面的逻辑中。
        if (factoryBean instanceof ScopedProxyFactoryBean) {
            // Scoped proxy factory beans are a special case and should not be further proxied
        }
        else {
            // It is a candidate FactoryBean - go ahead with enhancement
            return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
        }
    }

    if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
        // 上面这个用于判断当前的工厂方法,也就是@Bean标注的方法是否是在调用中。如果是在调用中,则说明需要真正的实例化了,此时调用父类真是方法来创建实例。
        // The factory is calling the bean method in order to instantiate and register the bean
        // (i.e. via a getBean() call) - invoke the super implementation of the method to actually
        // create the bean instance.
        if (logger.isWarnEnabled() &&
                BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
            // 如果是BeanFactoryPostProcessor类型的话则提出警告,表明可能并不能正确执行BeanFactoryPostProcessor的方法。
            logger.warn(String.format("@Bean method %s.%s is non-static and returns an object " +
                            "assignable to Spring's BeanFactoryPostProcessor interface. This will " +
                            "result in a failure to process annotations such as @Autowired, " +
                            "@Resource and @PostConstruct within the method's declaring " +
                            "@Configuration class. Add the 'static' modifier to this method to avoid " +
                            "these container lifecycle issues; see @Bean javadoc for complete details.",
                    beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
        }
        // 调用父类真实方法实例化。
        return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
    }
    // 这个方法尝试从beanFactory中获得目标bean,这样便可另所有此方法调用获得bean最终都是从beanFactory中获得的,达到了单例的目的。
    return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName);
    // 在Bean的方法A使用this引用调用方法B时,会先进入一次这个方法的逻辑,此时因为还没真正进行实例化,
    // isCurrentlyInvokedFactoryMethod(beanMethod)得到的结过是false,故会调用obtainBeanInstanceFromFactory,此时会从beanFactory中获得bean。
    // 在获得Bean时,会再次调用B方法,因为这个Bean需要调用@Bean的方法才能生成。调用前先打上正在调用的标记,同时再次进入这个方法逻辑,此时上面判断isCurrentlyInvokedFactoryMethod结过为true,调用父类方法进行真实的实例化。
}

/**
 * 该方法为FactoryBean返回被代理的新实例,新的实例拦截getObject方法,并从beanFactory中获得单例bean。
 */
private Object enhanceFactoryBean(final Object factoryBean, Class? exposedType,
        final ConfigurableBeanFactory beanFactory, final String beanName) {

    try {
        Class? clazz = factoryBean.getClass();
        boolean finalClass = Modifier.isFinal(clazz.getModifiers());
        boolean finalMethod = Modifier.isFinal(clazz.getMethod("getObject").getModifiers());
        // 判断真实FactoryBean的类型和getObject方法,如果是final的,说明不能通过CGLIB代理,则尝试使用JDK代理
        if (finalClass || finalMethod) {
            if (exposedType.isInterface()) {
                // 如果方法返回类型,即exposedType是接口,则这个接口一般都是FactoryBean,则通过jdk动态代理创建代理
                if (logger.isDebugEnabled()) {
                    logger.debug("Creating interface proxy for FactoryBean '" + beanName + "' of type [" +
                            clazz.getName() + "] for use within another @Bean method because its " +
                            (finalClass ? "implementation class" : "getObject() method") +
                            " is final: Otherwise a getObject() call would not be routed to the factory.");
                }
                return createInterfaceProxyForFactoryBean(factoryBean, exposedType, beanFactory, beanName);
            }
            else {
                // 不是接口就没办法了,只能直接返回原始的factoryBean,如果在这个factoryBean里getObject生成了新对象,多次调用生成的结果bean将不会是同一个实例。
                if (logger.isInfoEnabled()) {
                    logger.info("Unable to proxy FactoryBean '" + beanName + "' of type [" +
                            clazz.getName() + "] for use within another @Bean method because its " +
                            (finalClass ? "implementation class" : "getObject() method") +
                            " is final: A getObject() call will NOT be routed to the factory. " +
                            "Consider declaring the return type as a FactoryBean interface.");
                }
                return factoryBean;
            }
        }
    }
    catch (NoSuchMethodException ex) {
        // No getObject() method - shouldn't happen, but as long as nobody is trying to call it...
    }
    // 可以使用CGLIB代理类。
    return createCglibProxyForFactoryBean(factoryBean, beanFactory, beanName);
    // 假设A方法调用了@Bean的B方法,B方法返回FactoryBean实例
    // 那么在A调用B时,会先进入BeanMethodInterceptor.intercept方法
    // 在方法中判断目标bean是一个FactoryBean,且不是在创建中,则调用beanFactory的getBean尝试获取目标bean。
    // 在获取的过程中,最终又会执行方法B,此时被拦截再次进入这个intercept方法
    // 由于标记为创建中,故这里会进入下面的创建中逻辑,通过invokeSuper调用了真实的方法逻辑返回真实的FactoryBean。
    // 这个真实的FactoryBean返回之后,在第一次的intercept方法中,对这个FactoryBean实例进行代理,返回一个被代理的FactoryBean对象给方法A中的逻辑使用,这样就可以保证在A中调用FactoryBean.getObject时拿到的是beanFactory的bean实例了。
}

通过BeanMethodInterceptor.intercept方法,我们可以看到,真实的方法调用是通过cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs)来执行的,enhancedConfigInstance是动态代理产生的子类的实例,这里直接调用该对象的父类方法,即相当于调用的真实方法,这一点与Spring AOP体系中的把真实对象target作为真实调用实例来调用是有区别的,也就是这个区别,给this调用带来的上面的特性。

即在这种情况下this都是被CGLIB动态代理产生的子类的实例,在调用this.method()时,其实是调用了子类实例的该方法,此方法可以被方法拦截器拦截到,在拦截的逻辑中做一定的处理,如果需要调用真实对象的相应方法,直接使用invokeSuper来进行父类方法调用,而不是传入真实被动态代理对象的实例来进行调用。真实对象其实并没有创建,也就是说对应于Spring AOP,其中的target是不存在的,只有子类对象动态代理自身的实例,而没有真实对象实例。

由此我们便明了了this调用被动态拦截的实现方式。

对于上面Configuration的类的调用,可参考如下例子,对比调试后可以更加深入的理解这个问题。


import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TestConfig {

    @Bean
    public ConfigOut configOut() {
        Config c1 = this.config();
        Config c2 = this.config();
        // 这里返回同一个实例
        System.out.println(c1 == c2);
        ConfigOut configOut = new ConfigOut(this.config());
        FactoryBean ob1 = this.objectFactoryBean();
        FactoryBean ob2 = this.objectFactoryBean();
        // 这里也是 同一个实例
        System.out.println(ob1 == ob2);
        MyObject myObject1 = this.objectFactoryBean().getObject();
        MyObject myObject2 = this.objectFactoryBean().getObject();
        // 如果objectFactoryBean方法返回类型为FactoryBean则这两个相同
        // 如果是ObjectFactoryBean则两个不相同,上面已分析过原因
        System.out.println(myObject1 == myObject2);
        return configOut;
    }

    @Bean
    public Config config() {
        return new Config();
    }

    @Bean
    public FactoryBean objectFactoryBean() {
        return new ObjectFactoryBean();
    }

    public static class Config {}

    public static class ConfigOut {

        private Config config;

        private ConfigOut(Config config) {
            this.config = config;
        }

    }

    public static final class ObjectFactoryBean implements FactoryBeanMyObject {

        @Override
        public final MyObject getObject() {
            return new MyObject();
        }

        @Override
        public Class? getObjectType() {
            return MyObject.class;
        }

        @Override
        public boolean isSingleton() {
            return true;
        }
    }

    public static class MyObject {}

}

后记

本文根据实际场景,详细的分析了this调用导致AOP失效的原因,以及如何解决这个问题。并扩展了this调用可使AOP生效的场景。只要大家能理解到原理面,应该都能够分析出来原因。平时一些需要遵守的代码规范,在原理层面都是有其表现和原因的,分析真实原因得到最终结论,这个过程是对知识的升华过程,希望大家能够看到开心。

点击图片加入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失效的原因(下)