likes
comments
collection
share

【重写SpringFramework】第二章aop模块:自动代理下(chapter 2-7)

作者站长头像
站长
· 阅读数 25

1. 前言

上一节我们讨论了通用的自动代理组件 AbstractAutoProxyCreator,任何一个方法都可以与若干个增强逻辑组合起来使用。另外,AbstractAutoProxyCreator 还提供了两种创建代理的方式,它们作为主流程的补充,用于处理一些特殊的情况。本节我们将通过两个典型的使用场景进行分析。

2. 提前创建代理

2.1 代理对象的循环依赖

在讲解循环依赖时曾提到,对于普通对象来说,二级缓存就够用了,三级缓存是用于处理代理对象这种特殊单例的。当时没有展开,现在我们来分析一下代理对象在循环依赖中会出现什么问题,以及如何解决该问题。假设有两个类 TargetATargetB 互相依赖,且先获取 TargetA 的实例,会触发对 TargetB 的解析;在创建 TargetB 的过程中,又反过来解析 TargetA。具体流程如下图所示:

【重写SpringFramework】第二章aop模块:自动代理下(chapter 2-7)

整个流程提取出关键部分,大致可以分为五个步骤:

  1. TargetA 在创建中,开始解析 targetB 字段的依赖
  2. TargetB 在创建中,开始解析 targetA 字段的依赖
  3. 查询缓存,将三级缓存中的 TargetA 提升到二级缓存,并赋值给 TargetBtargetA 字段
  4. TargetB 完成依赖解析,在初始化的后置处理中创建代理对象,TargetB 创建完毕
  5. TargetA 完成依赖解析,在初始化的后置处理中创建代理对象,TargetA 创建完毕

我们发现,TargetATargetB 的代理对象最终都得到了创建,关键是第三步,对 TargetBtargetA 字段解析得到的是普通对象,而这就是问题所在。明确了问题之后,解决的办法就显而易见了。第三步中 ObjectFactory 的回调实际上执行的是 InstantiationAwareBeanPostProcessor 接口的 getEarlyBeanReference 方法,先前只是简单返回了 Bean 本身,现在只要返回对应的代理对象就行了。相应地,如果在第三步创建了 TargetA 的代理对象,那么第五步就会跳过常规的创建代理的流程。

2.2 代码实现

代码层面的改动很简单,只需要重写 AbstractAutoProxyCreator 类的 getEarlyBeanReference 方法。此外,对比 postProcessAfterInitialization 方法,可以发现两者的实现几乎完全一致。earlyProxyReferences 字段保证了这两个操作是互斥的,getEarlyBeanReference 方法优先执行,这也是方法名中 early 的含义。

public abstract class AbstractAutoProxyCreator {
    private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);

    //提前创建代理
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = StringUtils.hasLength(beanName) ? beanName : bean.getClass();
        this.earlyProxyReferences.put(cacheKey, bean);
        return wrapIfNecessary(bean, beanName, cacheKey);
    }

    //初始化后创建代理
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        Object cacheKey = StringUtils.hasLength(beanName) ? beanName : bean.getClass();

        //如果没有在getEarlyBeanReference时提前实例化,则创建aop代理
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
        return null;
    }
}

3. 实例化前创建代理

3.1 概述

BeanDefinition 中有一个 lazyInit 属性,作用是延迟初始化实例。具体说来,调用 getBean 方法时并不会触发创建流程,只有在第一次使用(调用方法)时才会创建单例。延迟初始化的实现有多种方式,其中一种与自动代理有关。InstantiationAwareBeanPostProcessor 接口定义了 postProcessBeforeInstantiation 方法,调用时机是在创建对象之前,如果得到了一个代理,直接返回并添加到容器中。我们知道 AopProxy 在调用任意方法时都会触发拦截逻辑,因此当第一次调用方法时,才会执行目标对象的创建流程。

前边提到,目标对象被抽象成 TargetSource 接口,在创建代理的流程中,通常使用 SingletonTargetSource 对单例进行包装。除了直接使用实现类之外,还可以通过 TargetSourceCreator 接口以工厂方法的方式获取 TargetSource。我们先来相关的类图结构,AbstractAutoProxyCreator 持有一个 TargetSourceCreator 的数组,TargetSourceCreator 使用工厂方法模式创建 TargetSource 实例。

【重写SpringFramework】第二章aop模块:自动代理下(chapter 2-7)

3.2 TargetSourceCreator

TargetSourceCreator 接口定义了创建 TargetSource 的方法,抽象子类 AbstractBeanFactoryBasedTargetSourceCreator 的特殊之处在于持有两个 Spring 容器,其中 beanFactory 字段表示外层容器,是由 setter 方法指定的;internalBeanFactory 字段表示内置的 Spring 容器。外层容器是应用的主容器,存储的是代理对象,内层容器的作用是创建目标对象。

AbstractBeanFactoryBasedTargetSourceCreator 实现了 TargetSourceCreator 接口的 getTargetSource 方法,可以分为三步:

  1. 创建 TargetSource 实例,具体工作由子类完成。
  2. 将目标对象的 BeanDefinition 注册到内层容器,这一点很重要,因为目标对象是由内层容器创建的。
  3. TargetSource 实例进行设置,需要注意的是,TargeSource 持有的是内层容器,这是为之后创建目标对象做准备。
public abstract class AbstractBeanFactoryBasedTargetSourceCreator implements TargetSourceCreator, BeanFactoryAware {
    private ConfigurableBeanFactory beanFactory;
    private final DefaultListableBeanFactory internalBeanFactory = new DefaultListableBeanFactory();

    @Override
    public TargetSource getTargetSource(Class<?> beanClass, String beanName) {
        //1. 创建TargetSource实例,由子类完成
        AbstractBeanFactoryBasedTargetSource targetSource = createBeanFactoryBasedTargetSource(beanClass, beanName);
        if (targetSource == null) {
            return null;
        }

        //2. 从外层容器中取出目标对象的BeanDefinition,并注册到内置容器中
        BeanDefinition bd = this.beanFactory.getMergedBeanDefinition(beanName);
        RootBeanDefinition definition = new RootBeanDefinition(bd);
        this.internalBeanFactory.registerBeanDefinition(beanName, definition);

        //3. 对TargetSource实例进行设置
        targetSource.setTargetBeanName(beanName);
        targetSource.setBeanFactory(this.internalBeanFactory);
        return targetSource;
    }
}

LazyInitTargetSourceCreator 重写了父类创建 TargetSource 的方法。首先获取目标对象的 BeanDefinition,如果需要延迟初始化则创建 LazyInitTargetSource 对象,否则返回空。

public class LazyInitTargetSourceCreator extends AbstractBeanFactoryBasedTargetSourceCreator {

    @Override
    protected AbstractBeanFactoryBasedTargetSource createBeanFactoryBasedTargetSource(Class<?> beanClass, String beanName) {
        BeanDefinition definition = getBeanFactory().getBeanDefinition(beanName);
        //判断lazyInit的属性是否为true
        if (definition.isLazyInit()) {
            return new LazyInitTargetSource();
        }
        return null;
    }
}

3.3 TargetSource

AbstractBeanFactoryBasedTargetSource 的特点是持有一个 BeanFactory 实例,这是一个独立的 Spring 容器,承担了目标对象的管理工作。

public abstract class AbstractBeanFactoryBasedTargetSource implements TargetSource {
    private String targetBeanName;
    private Class<?> targetClass;
    private BeanFactory beanFactory;
}

LazyInitTargetSource 作为实现类完成了延迟初始化的一部分工作。target 字段表示目标对象,getTarget 方法实现了延迟初始化的功能。当该方法被调用时,才会调用 BeanFactorygetBean 方法,完成实际的创建工作。

public class LazyInitTargetSource extends AbstractBeanFactoryBasedTargetSource {
    private Object target;

    @Override
    public Object getTarget() throws Exception {
        if (this.target == null) {
            //执行目标对象的创建工作
            this.target = getBeanFactory().getBean(getTargetBeanName());
        }
        return this.target;
    }
}

3.4 AbstractAutoProxyCreator 的实现

AbstractAutoProxyCreator 类新增了两个字段,customTargetSourceCreators 字段表示 TargetSourceCreator 数组,targetSourcedBeans 字段表示指定名称的单例是否已经被处理。

postProcessBeforeInstantiation 方法的执行时机是在对象实例化之前,如果返回值不为空,就不会继续接下来的流程,也就是说 Spring 容器中存储的是一个代理对象。我们来看方法是如何实现的,第一步是前置检查,这里使用 targetSourcedBeans 字段确保该操作只会执行一次。第二步是创建代理的流程,先创建 TargetSource 实例,然后获取拦截器链,最终创建代理对象并返回。

public abstract class AbstractAutoProxyCreator {
    private TargetSourceCreator[] customTargetSourceCreators;
    private final Set<String> targetSourcedBeans = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

    //实例化前创建代理对象
    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        //前置检查
        Object cacheKey = StringUtils.hasLength(beanName) ? beanName : beanClass;
        if (!this.targetSourcedBeans.contains(beanName)) {
            if (this.advisedBeans.containsKey(cacheKey)) {
                return null;
            }
            if (isInfrastructureClass(beanClass)) {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return null;
            }
        }

        //使用自定义的TargetSource创建代理
        TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
        if (targetSource != null) {
            this.targetSourcedBeans.add(beanName);
            //获取拦截器链
            Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
            //创建代理对象
            Object proxy = createProxy(beanClass, specificInterceptors, targetSource);
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }
        return null;
    }
}

第二步看起来一切都按部就班,关键在于判断一个类是否应该被代理的规则不同。回顾一下另外两种创建方式,在 wrapIfNecessary 方法中创建代理对象的依据是 specificInterceptors 不能为空,也就是说增强逻辑必须存在。但是 postProcessBeforeInstantiation 方法判断的依据是 TargetSource 是否存在,也就是说 TargetSource 提供了特定的增强逻辑。因此不需要再判断拦截器链是否为空,都会创建代理对象。

换个角度来看,对于延迟初始化的对象来说,一开始得到的是代理对象,只有在调用任意方法时,才会触发目标对象的创建流程。要实现这一点,必须对目标对象进行代理,即使它只是一个普通对象。同样地,对于本来就需要代理的对象来说,也算是一举两得了。

3.5 深度思考

经过上述的分析,在实例化 Bean 之前尝试创建代理对象,如果存在则将代理注册到外层容器中。代理对象的 TargetSource 持有一个内层容器,并在适当的时机创建目标对象。从下图中可以看到,这种方式实际上和 FactoryBean 的存储结构有些类似,都是将包装对象和目标对象分开存放。这种结构很好理解,如果没有内层容器,TargetSource 中存储的仍是代理对象,永远都无法拿到目标对象。

【重写SpringFramework】第二章aop模块:自动代理下(chapter 2-7)

需要注意的是,依托 LazyTargetSource 提供的延迟初始化功能和其他的增强逻辑是可以共存的,这种双重增强是建立在 AopProxy 回调的基础之上,我们以 JdkDynamicAopProxy 为例来说明。在 invoke 方法中,TargetSource 接口的回调方法是第一次增强,拦截器链则是第二次增强。事实上,第二次才是通常意义上的增强,TargetSource 实现的增强是一种取巧的手段,只能完成一些辅助性的功能,想要实现复杂的业务逻辑还是得通过第二次增强。

//所属类[cn.stimd.spring.aop.framework.JdkDynamicAopProxy]
//AOP代理的回调
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //第一次增强
    Object target = targetSource.getTarget();
    //第二次增强
    List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
    ......
}

4. 测试

4.1 未解决的循环依赖

由于 AbstractAutoProxyCreator 已经实现了 getEarlyBeanReference 的功能,因此我们需要一个子类重写该方法,模拟未实现提前暴露代理的情况。

//测试类:模拟未解决循环依赖的情况
public class UnresolvedCircleReferenceAutoProxyCreator extends InfrastructureAdvisorAutoProxyCreator {

    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) {
        return bean;
    }
}

接下来是 TargetATargetB 互相依赖,需要注意的是,TargetBhandle 方法调用了 TargetAhandle 方法。如果 TargetB 中的 targetA 指向的是代理对象,那么 TargetAhandle 会被日志拦截,否则不会出现日志拦截。

//测试类
public class TargetA {
    @Autowired
    private TargetB targetB;


    @Logger
    public void handle() {
        System.out.println("执行目标方法...");
    }
}

public class TargetB {
    @Autowired
    private TargetA targetA;


    @Logger
    public void handle() {
        this.targetA.handle();
    }
}

在测试方法中,需要注意三点。一是自动代理组件使用的是 UnresolvedCircleReferenceAutoProxyCreator 实例,二是先获取 TargetA 的实例,是为了确保 TargetB 在依赖解析时拿到的 TargetA 是一个普通对象。三是单独调用 TargetAhandle 方法,说明 TargetA 本身是一个代理。

//测试方法
@Test
public void testUnresolvedCircleReference() {
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    //注册Advisor
    RootBeanDefinition definition = new RootBeanDefinition(LoggerAdvisor.class);
    definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    factory.registerBeanDefinition("loggerAdvisor", definition);

    //注册自动代理组件
    InfrastructureAdvisorAutoProxyCreator proxyProcessor = new UnresolvedCircleReferenceAutoProxyCreator();	//1)模拟未解决代理的依赖注入
    proxyProcessor.setBeanFactory(factory);
    factory.addBeanPostProcessor(proxyProcessor);

    //注册依赖注入组件
    AutowiredAnnotationBeanPostProcessor autowiredProcessor = new AutowiredAnnotationBeanPostProcessor();
    autowiredProcessor.setBeanFactory(factory);
    factory.addBeanPostProcessor(autowiredProcessor);

    //注册需要代理的目标对象
    TargetA targetA = factory.getBean("targetA", TargetA.class);	//2)优先获取A,确保正确的依赖关系
    TargetB targetB = factory.getBean("targetB", TargetB.class);
    targetB.handle();

    System.out.println("--------------------");
    targetA.handle();	//3)单独调用,说明TargetA本身是一个代理(TargetB的targetA字段指向的不是代理)
}

从测试结果来看,由于测试方法中先获取 TargetA,因此先创建的是 TargetB 的代理,后创建 TargetA 的代理,这是符合依赖关系的。第三行说明执行了 TargetBhandle 方法,且带有日志拦截。第四行说明 TargetAhandle 方法执行了,但没有日志拦截。第五行是分隔符,接下来单独调用 TargetAhandle 方法。第六行和第七行说明 TargetA 本身的确是一个代理对象。

[Aop] [创建代理对象] --> 目标对象: TargetB
[Aop] [创建代理对象] --> 目标对象: TargetA
日志拦截: TargetB 方法:handle
执行目标方法...
--------------------
日志拦截: TargetA 方法:handle
执行目标方法...

4.2 已解决的循环依赖

测试方法大同小异,自动代理组件恢复成正常的 InfrastructureAdvisorAutoProxyCreator

//测试方法
@Test
public void testResolvedCircleReference() {
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    //注册Advisor
    RootBeanDefinition definition = new RootBeanDefinition(LoggerAdvisor.class);
    definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    factory.registerBeanDefinition("loggerAdvisor", definition);

    //注册自动代理组件
    InfrastructureAdvisorAutoProxyCreator proxyProcessor = new InfrastructureAdvisorAutoProxyCreator();
    proxyProcessor.setBeanFactory(factory);
    factory.addBeanPostProcessor(proxyProcessor);

    //注册依赖注入组件
    AutowiredAnnotationBeanPostProcessor autowiredProcessor = new AutowiredAnnotationBeanPostProcessor();
    autowiredProcessor.setBeanFactory(factory);
    factory.addBeanPostProcessor(autowiredProcessor);

    //注册需要代理的目标对象
    factory.registerBeanDefinition("targetA", new RootBeanDefinition(TargetA.class));
    factory.registerBeanDefinition("targetB", new RootBeanDefinition(TargetB.class));

    factory.getBean("targetA", TargetA.class);
    TargetB targetB = factory.getBean("targetB", TargetB.class);
    targetB.handle();
}

从测试结果来看,第一行最重要,说明在解析 TargetA 的依赖时,提前创建了代理对象。之后调用 TargetAhandle 方法时执行了日志拦截,说明代理的循环依赖问题得到了解决。

[Aop] [提前暴露代理] --> 目标对象: TargetA
[Aop] [创建代理对象] --> 目标对象: TargetA
[Aop] [创建代理对象] --> 目标对象: TargetB
日志拦截: TargetB 方法:handle
日志拦截: TargetA 方法:handle
执行目标方法...

4.3 延迟初始化

测试类继续使用 LoggerTarget,添加了无参的构造函数,用于观察目标对象是何时创建的。

//测试类
public class LoggerTarget {

    public LoggerTarget() {
        System.out.println("调用LoggerTarget构造器...");
    }
}

测试方法重点关注第三步和第四步,第三步创建了 LazyInitTargetSourceCreator 实例,并添加到自动代理组件中,确保在实例化前创建代理对象。第四步创建目标对象的 BeanDefinition,重点是将 lazyInit 属性设置为 true,确保创建 LazyTargetSource 对象。

//测试方法
@Test
public void testLazyInitProxy() {
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    //1. 注册Advisor
    RootBeanDefinition advisorDefinition = new RootBeanDefinition(LoggerAdvisor.class);
    advisorDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    factory.registerBeanDefinition("loggerAdvisor", advisorDefinition);

    //2. 注册自动代理组件
    InfrastructureAdvisorAutoProxyCreator processor = new InfrastructureAdvisorAutoProxyCreator();
    processor.setBeanFactory(factory);
    factory.addBeanPostProcessor(processor);

    //3. 注册TargetSourceCreator
    LazyInitTargetSourceCreator creator = new LazyInitTargetSourceCreator();
    creator.setBeanFactory(factory);
    processor.setCustomTargetSourceCreators(creator);

    //4. 注册需要代理的目标对象
    RootBeanDefinition beanDefinition = new RootBeanDefinition(LoggerTarget.class);
    beanDefinition.setLazyInit(true);       //开启延迟加载
    factory.registerBeanDefinition("target", beanDefinition);

    LoggerTarget target = factory.getBean("target", LoggerTarget.class);
    System.out.println("执行getBean方法");
    target.handle();
}

测试结果很重要,我们来详细分析一下。第一行说明在实例化之前执行了创建代理的操作。第二行说明 LoggerTaget 是一个代理对象,此时并没有打印构造函数的日志,说明实际的对象并未创建。接下来执行了代理对象的 handle 方法,这时会触发 AOP 代理的回调,首先执行的是 LazyInitTargetSourcegetTarget 方法,触发内置容器的 getBean 操作,从第三行日志可以看出,此时创建了目标对象。第四行是正常的日志拦截逻辑,第五行是目标方法的调用。

注:从上述分析中,我们印证了之前提到的几个结论。一是外层容器存储的是代理对象,二是代理对象的任何操作都会获取目标对象,且初次访问会创建目标对象,三是延迟初始化和日志拦截这两种不同类型的增强是可以共存的。

[Aop] [自定义TargetSource代理] --> 目标对象: target
单例类型:LoggerTarget$$EnhancerBySpringCGLIB$$679d5313
调用LoggerTarget构造器...
日志拦截: LoggerTarget 方法:handle
执行handle...

5. 总结

AbstractAutoProxyCreator 作为创建代理对象的核心组件,提供了三种创建代理对象的方式,按照代码调用的顺序排列如下:

  • postProcessBeforeInstantiation:在对象实例化前执行,比如延迟初始化
  • getEarlyBeanReference:在对象填充之前执行,提前创建代理对象,主要解决代理对象的循环依赖问题
  • postProcessAfterInitialization:在对象初始化后执行,最常用的创建代理的方式

上一节先介绍了第三种情况,因为这是最常用的创建代理的方式,本节我们讨论了另外两种情况。为了保证创建代理的操作只执行一次,它们之间是互斥的

第一种情况比较特殊,不仅创建了代理对象,且拥有双重的增强。具体来说,第一次增强是伪增强,是由 TargetSource 接口的回调提供的,第二次增强才是通常意义上的增强,由拦截器链实现。在测试代码中,我们实现了延迟初始化和日志拦截,前者是伪增强,后者是实际的增强。

6. 项目信息

新增修改一览,新增(8),修改(3)。

aop
└─ src
   ├─ main
   │  └─ java
   │     └─ cn.stimd.springwheel.aop
   │        ├─ framework
   │        │  ├─ autoproxy
   │        │  │  ├─ target
   │        │  │  │  ├─AbstractBeanFactoryBasedTargetSourceCreator.java(+)
   │        │  │  │  └─LazyInitTargetSourceCreator.java(+)
   │        │  │  └─ TargetSourceCreator.java(+)
   │        │  └─ AbstractAutoProxyCreator.java (*)
   │        └─ target
   │           ├─ AbstractBeanFactoryBasedTargetSource.java (+)
   │           └─ LazyInitTargetSource.java (+)
   └─ test
      └─ java
         └─ aop.test
            └─ proxy
               ├─ autoproxy
               │  ├─LoggerTarget.java(*)
               │  ├─TargetA.java(+)
               │  ├─TargetB.java(+)
               │  └─UnresolvedCircleReferenceAutoProxyCreator.java(+)
               └─ ProxyTest.java (*)

注:+号表示新增、*表示修改

注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。


欢迎关注公众号【Java编程探微】,加群一起讨论。

原创不易,觉得内容不错请关注、点赞、收藏。

转载自:https://juejin.cn/post/7393194468646256675
评论
请登录