【重写SpringFramework】第二章aop模块:自动代理下(chapter 2-7)
1. 前言
上一节我们讨论了通用的自动代理组件 AbstractAutoProxyCreator
,任何一个方法都可以与若干个增强逻辑组合起来使用。另外,AbstractAutoProxyCreator
还提供了两种创建代理的方式,它们作为主流程的补充,用于处理一些特殊的情况。本节我们将通过两个典型的使用场景进行分析。
2. 提前创建代理
2.1 代理对象的循环依赖
在讲解循环依赖时曾提到,对于普通对象来说,二级缓存就够用了,三级缓存是用于处理代理对象这种特殊单例的。当时没有展开,现在我们来分析一下代理对象在循环依赖中会出现什么问题,以及如何解决该问题。假设有两个类 TargetA
和 TargetB
互相依赖,且先获取 TargetA
的实例,会触发对 TargetB
的解析;在创建 TargetB
的过程中,又反过来解析 TargetA
。具体流程如下图所示:
整个流程提取出关键部分,大致可以分为五个步骤:
TargetA
在创建中,开始解析targetB
字段的依赖TargetB
在创建中,开始解析targetA
字段的依赖- 查询缓存,将三级缓存中的
TargetA
提升到二级缓存,并赋值给TargetB
的targetA
字段 TargetB
完成依赖解析,在初始化的后置处理中创建代理对象,TargetB
创建完毕TargetA
完成依赖解析,在初始化的后置处理中创建代理对象,TargetA
创建完毕
我们发现,TargetA
和 TargetB
的代理对象最终都得到了创建,关键是第三步,对 TargetB
的 targetA
字段解析得到的是普通对象,而这就是问题所在。明确了问题之后,解决的办法就显而易见了。第三步中 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
实例。
3.2 TargetSourceCreator
TargetSourceCreator
接口定义了创建 TargetSource
的方法,抽象子类 AbstractBeanFactoryBasedTargetSourceCreator
的特殊之处在于持有两个 Spring 容器,其中 beanFactory
字段表示外层容器,是由 setter 方法指定的;internalBeanFactory
字段表示内置的 Spring 容器。外层容器是应用的主容器,存储的是代理对象,内层容器的作用是创建目标对象。
AbstractBeanFactoryBasedTargetSourceCreator
实现了 TargetSourceCreator
接口的 getTargetSource
方法,可以分为三步:
- 创建
TargetSource
实例,具体工作由子类完成。 - 将目标对象的
BeanDefinition
注册到内层容器,这一点很重要,因为目标对象是由内层容器创建的。 - 对
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
方法实现了延迟初始化的功能。当该方法被调用时,才会调用 BeanFactory
的 getBean
方法,完成实际的创建工作。
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
中存储的仍是代理对象,永远都无法拿到目标对象。
需要注意的是,依托 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;
}
}
接下来是 TargetA
和 TargetB
互相依赖,需要注意的是,TargetB
的 handle
方法调用了 TargetA
的 handle
方法。如果 TargetB
中的 targetA
指向的是代理对象,那么 TargetA
的 handle
会被日志拦截,否则不会出现日志拦截。
//测试类
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
是一个普通对象。三是单独调用 TargetA
的 handle
方法,说明 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
的代理,这是符合依赖关系的。第三行说明执行了 TargetB
的 handle
方法,且带有日志拦截。第四行说明 TargetA
的 handle
方法执行了,但没有日志拦截。第五行是分隔符,接下来单独调用 TargetA
的 handle
方法。第六行和第七行说明 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
的依赖时,提前创建了代理对象。之后调用 TargetA
的 handle
方法时执行了日志拦截,说明代理的循环依赖问题得到了解决。
[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 代理的回调,首先执行的是 LazyInitTargetSource
的 getTarget
方法,触发内置容器的 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