AOP原理
1、AOP功能测试
什么是AOP
AOP(Aspect Orient Programming),直译过来就是面向切面编程。AOP是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面
如下一张图描述了AOP的大体模型
从这张图中,我们可以看出:所谓切面,其实就相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块。
总之一句话:AOP是指在程序的运行期间动态地将某段代码切入到指定方法、指定位置进行运行的编程方式。AOP的底层是使用动态代理实现的。
案例实战
1、导入AOP依赖
要想搭建AOP环境,首先,我们就需要在项目的pom.xml文件中引入AOP的依赖,如下所示。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.21</version>
</dependency>
其实,Spring AOP对面向切面编程做了一些简化操作,我们只需要加上几个核心注解,AOP就能工作起来。
2、定义目标类
在com.jian.aop包下创建一个业务逻辑类,例如MathCalculator,用于处理数学计算上的一些逻辑。比如,我们在MathCalculator类中定义了一个除法操作,返回两个整数类型值相除之后的结果,如下所示
package com.jian.aop;
public class MathCalculator {
public int div(int i, int j) {
System.out.println("MathCalculator...div...");
return i / j;
}
}
现在,我们希望在以上这个业务逻辑类中的除法运算之前,记录一下日志,例如记录一下哪个方法运行了,用的参数是什么,运行结束之后它的返回值又是什么,顺便可以将其打印出来,还有如果运行出异常了,那么就捕获一下异常信息。
或者,你会有这样一个需求,即希望在业务逻辑运行的时候将日志进行打印,而且是在方法运行之前、方法运行结束、方法出现异常等等位置,都希望会有日志打印出来。
3、定义切面类
在com.meimeixia.aop包下创建一个切面类,例如LogAspects,在该切面类中定义几个打印日志的方法,以这些方法来动态地感知MathCalculator类中的div()方法的运行情况。如果需要切面类来动态地感知目标类方法的运行情况,那么就需要使用Spring AOP中的一系列通知方法了。
AOP中的通知方法及其对应的注解与含义如下:
- 前置通知(对应的注解是@Before):在目标方法运行之前运行
- 后置通知(对应的注解是@After):在目标方法运行结束之后运行,无论目标方法是正常结束还是异常结束都会执行
- 返回通知(对应的注解是@AfterReturning):在目标方法正常返回之后运行
- 异常通知(对应的注解是@AfterThrowing):在目标方法运行出现异常之后运行
- 环绕通知(对应的注解是@Around):动态代理,我们可以直接手动推进目标方法运行(joinPoint.procced())
package com.jian.aop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Before;
public class LogAspects {
// @Before:在目标方法(即div方法)运行之前切入,public int com.meimeixia.aop.MathCalculator.div(int, int)这一串就是切入点表达式,指定在哪个方法切入
@Before("execution(public int com.jian.aop.MathCalculator.*(..))")
public void logStart() {
System.out.println("除法运行......@Before,参数列表是:{}");
}
//在目标方法(div方法)结束时被调用
@After("execution(public int com.jian.aop.MathCalculator.*(..))")
public void logEnd() {
System.out.println("除法结束......@After");
}
// 在目标方法(即div方法)正常返回了,有返回值,被调用
@AfterReturning("execution(public int com.jian.aop.MathCalculator.*(..))")
public void logReturn() {
System.out.println("除法返回......@AfterReturning,运行结果是:{}");
}
// 在目标方法(即div方法)出现异常,被调用
@AfterThrowing("execution(public int com.jian.aop.MathCalculator.*(..))")
public void logException() {
System.out.println("除法出现异常......异常信息:{}");
}
}
由于我们现在是对MathCalculator类中的div()方法进行切入,因此在每一个通知方法上都写了execution(public int com.jian.aop.MathCalculator.*(…))这样一串玩意,其实它就是切入点表达式,即指定在哪个方法切入。但是,你不觉得在每一个通知方法上都写上这样一串玩意,很无聊吗?
如果切入点表达式都一样的情况下,那么我们可以抽取出一个公共的切入点表达式,就像下面这样。
package com.jian.aop;
import org.aspectj.lang.annotation.*;
public class LogAspects {
@Pointcut("execution(public int com.jian.aop.MathCalculator.*(..))")
public void pointCut() {
}
//.......
}
pointCut()方法就是抽取出来的一个公共的切入点表达式,其实该方法的方法名随便写啥都行,但是方法体中啥都别写。
那么问题来了,如何在每一个通知方法上引用这个公共的切入点表达式呢?这得分两种情况来讨论,第一种情况,如果是本类引用,那么可以像下面这样写
package com.jian.aop;
import org.aspectj.lang.annotation.*;
public class LogAspects {
@Pointcut("execution(public int com.jian.aop.MathCalculator.*(..))")
public void pointCut() {
}
// @Before:在目标方法(即div方法)运行之前切入,public int com.meimeixia.aop.MathCalculator.div(int, int)这一串就是切入点表达式,指定在哪个方法切入
//@Before("public int com.jian.aop.MathCalculator.*(..)")
@Before("pointCut()")
public void logStart() {
System.out.println("除法运行......@Before,参数列表是:{}");
}
//......
}
第二种情况,如果是外部类(即其他的切面类)引用,那么就得在通知注解中写方法的全名了,例如
package com.jian.aop;
import org.aspectj.lang.annotation.*;
public class LogAspects {
@Pointcut("execution(public int com.jian.aop.MathCalculator.*(..))")
public void pointCut() {
}
// @Before:在目标方法(即div方法)运行之前切入,public int com.meimeixia.aop.MathCalculator.div(int, int)这一串就是切入点表达式,指定在哪个方法切入
//@Before("public int com.jian.aop.MathCalculator.*(..)")
@Before("pointCut()")
public void logStart() {
System.out.println("除法运行......@Before,参数列表是:{}");
}
//在目标方法(div方法)结束时被调用
//@After("public int com.jian.aop.MathCalculator.*(..)")
@After("com.jian.aop.LogAspects.pointCut()")
public void logEnd() {
System.out.println("除法结束......@After");
}
//.......
}
最后,千万别忘了一点,那就是必须告诉Spring哪个类是切面类,要做到这一点很简单,只需要给切面类上加上一个@Aspect注解即可
package com.jian.aop;
import org.aspectj.lang.annotation.*;
@Aspect
public class LogAspects {
@Pointcut("execution(public int com.jian.aop.MathCalculator.*(..))")
public void pointCut() {
}
// @Before:在目标方法(即div方法)运行之前切入,public int com.meimeixia.aop.MathCalculator.div(int, int)这一串就是切入点表达式,指定在哪个方法切入
//@Before("public int com.jian.aop.MathCalculator.*(..)")
@Before("pointCut()")
public void logStart() {
System.out.println("除法运行......@Before,参数列表是:{}");
}
//在目标方法(div方法)结束时被调用
//@After("public int com.jian.aop.MathCalculator.*(..)")
@After("com.jian.aop.LogAspects.pointCut()")
public void logEnd() {
System.out.println("除法结束......@After");
}
// 在目标方法(即div方法)正常返回了,有返回值,被调用
@AfterReturning("execution(public int com.jian.aop.MathCalculator.*(..))")
public void logReturn() {
System.out.println("除法返回......@AfterReturning,运行结果是:{}");
}
// 在目标方法(即div方法)出现异常,被调用
@AfterThrowing("pointCut()")
public void logException() {
System.out.println("除法出现异常......异常信息:{}");
}
}
至此,切面类的完整代码我们就写出来了。
4、将目标类和切面类加入到IOC容器中
在com.meimeixia.config包中,新建一个配置类,例如MainConfigOfAOP,并使用@Configuration注解标注这是一个Spring的配置类,同时使用@EnableAspectJAutoProxy注解开启基于注解的AOP模式。在MainConfigOfAOP配置类中,使用@Bean注解将业务逻辑类(目标方法所在类)和切面类都加入到IOC容器中,如下所示
package com.jian.config;
import com.jian.aop.LogAspects;
import com.jian.aop.MathCalculator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* AOP:面向切面编程,其底层就是动态代理
* 指在程序运行期间动态地将某段代码切入到指定方法指定位置进行运行的编程方式。
*
* @author Jsonjian
*
*/
@Configuration
@EnableAspectJAutoProxy
public class MainConfigOfAOP {
// 将业务逻辑类(目标方法所在类)加入到容器中
@Bean
public MathCalculator mathCalculator() {
return new MathCalculator();
}
// 将切面类加入到容器中
@Bean
public LogAspects logAspects() {
return new LogAspects();
}
}
一定不要忘了给MainConfigOfAOP配置类标注@EnableAspectJAutoProxy注解哟!在Spring中,未来会有很多的@EnableXxx注解,它们的作用都是开启某一项功能,来替换我们以前的那些配置文件
5、测试
首先,在com.jian.test包中创建一个单元测试类,例如IOCTest_AOP,并在该测试类中创建一个test01()方法,如下所示。
public class AOPTest {
@Test
public void test01() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);
//自己创建的业务逻辑类,无法使用切面相关功能
//MathCalculator mathCalculator = new MathCalculator();
//mathCalculator.div(1, 1);
//我们要从容器中获取
MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class);
mathCalculator.div(1, 1);
//关闭容器
applicationContext.close();
}
}
运行结果如下
可以看到,执行了切面类中的方法,并打印出了相关信息。但是并没有打印参数列表和运行结果。
如果需要在切面类中打印出参数列表和运行结果,那么该怎么办呢?别急,我们继续往下看。
要想打印出参数列表和运行结果,就需要对LogAspects切面类中的方法进行优化,优化后的结果如下所示。
package com.jian.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Arrays;
@Aspect
public class LogAspects {
@Pointcut("execution(public int com.jian.aop.MathCalculator.*(..))")
public void pointCut() {
}
// @Before:在目标方法(即div方法)运行之前切入,public int com.meimeixia.aop.MathCalculator.div(int, int)这一串就是切入点表达式,指定在哪个方法切入
//@Before("public int com.jian.aop.MathCalculator.*(..)")
@Before("pointCut()")
public void logStart(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs(); // 拿到参数列表,即目标方法运行需要的参数列表
System.out.println(joinPoint.getSignature().getName()+"运行......@Before,参数列表是{"+ Arrays.asList(args)+"}");
//System.out.println("除法运行......@Before,参数列表是:{}");
}
//在目标方法(div方法)结束时被调用
//@After("public int com.jian.aop.MathCalculator.*(..)")
@After("com.jian.aop.LogAspects.pointCut()")
public void logEnd(JoinPoint joinPoint) {
//System.out.println("除法结束......@After");
System.out.println(joinPoint.getSignature().getName()+"结束......@After");
}
// 在目标方法(即div方法)正常返回了,有返回值,被调用
@AfterReturning(value = "execution(public int com.jian.aop.MathCalculator.*(..))",returning = "result") //returning来指定我们这个方法的参数谁来封装返回值
public void logReturn(JoinPoint joinPoint,Object result) { // 一定要注意:JoinPoint这个参数要写,一定不能写到后面,它必须出现在参数列表的第一位,否则Spring也是无法识别的,就会报错
//System.out.println("除法返回......@AfterReturning,运行结果是:{}");
System.out.println(joinPoint.getSignature().getName()+"正常返回......@AfterReturning,运行结果是:{"+result+"}");
}
// 在目标方法(即div方法)出现异常,被调用
@AfterThrowing("pointCut()")
public void logException() {
System.out.println("除法出现异常......异常信息:{}");
}
}
这里,需要注意的是,JoinPoint参数一定要放在参数列表的第一位,否则Spring是无法识别的,那自然就会报错了。
此时再运行测试test01
如果目标方法运行时出现了异常,而我们又想拿到这个异常信息,那么该怎么办呢?只须对LogAspects切面类中的logException()方法进行优化即可,优化后的结果如下所示。
// 在目标方法(即div方法)出现异常,被调用
@AfterThrowing(value = "pointCut()",throwing = "exception")
public void logException(JoinPoint joinPoint,Exception exception) {
//System.out.println("除法出现异常......异常信息:{}");
System.out.println(joinPoint.getSignature().getName()+"出现异常......异常信息:{"+exception+"}");
}
可以看到,JoinPoint参数是放在了参数列表的第一位。
接下来,我们就在MathCalculator类的div()方法中模拟抛出一个除零异常,来测试下异常情况,如下所示。
@Test
public void test01() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);
//自己创建的业务逻辑类,无法使用切面相关功能
//MathCalculator mathCalculator = new MathCalculator();
//mathCalculator.div(1, 1);
//我们要从容器中获取
MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class);
mathCalculator.div(1, 0);
//关闭容器
applicationContext.close();
}
可以看到,正确的输出了切面中打印的信息,包括除零异常的信息。
小结
- 将切面类和业务逻辑组件(目标方法所在类)都加入到容器中,并且要告诉Spring哪个类是切面类(标注了@Aspect注解的那个类)。
- 在切面类上的每个通知方法上标注通知注解,告诉Spring何时何地运行,当然最主要的是要写好切入点表达式,这个切入点表达式可以参照官方文档来写。
- 开启基于注解的AOP模式,即加上@EnableAspectJAutoProxy注解,这是最关键的一点
2、@EnableAspectAutoProxy注解
在配置类上添加@EnableAspectJAutoProxy注解,便能够开启注解版的AOP功能。也就是说,如果要使注解版的AOP功能起作用的话,那么就得需要在配置类上添加@EnableAspectJAutoProxy注解。我们先来看下@EnableAspectJAutoProxy注解的源码,如下所示
从源码中可以看出,@EnableAspectJAutoProxy注解使用@Import注解给容器中引入了AspectJAutoProxyRegister组件。那么,这个AspectJAutoProxyRegistrar组件又是什么呢?我们继续点进去到AspectJAutoProxyRegistrar类的源码中,如下所示
可以看到AspectJAutoProxyRegistrar类实现了ImportBeanDefinitionRegistrar接口。我们再点进去到ImportBeanDefinitionRegistrar接口的源码中,如下所示。
我们可以通过ImportBeanDefinitionRegistrar接口实现将自定义的组件添加到IOC容器中
也就是说,@EnableAspectJAutoProxy注解使用AspectJAutoProxyRegistrar对象自定义组件,并将相应的组件添加到了IOC容器中。
那么,@EnableAspectJAutoProxy注解使用AspectJAutoProxyRegistrar对象向容器中注册了一个什么bean呢?接下来咱们就要说道说道了。
调试Spring源码
首先,我们需要给这个AspectJAutoProxyRegistrar类打一个断点,断点就打在该类的registerBeanDefinitions()方法处,如下所示。
然后,我们以debug的方式来运行AOPTest类中的test01()方法。运行后程序进入到断点位置,如下所示。
可以看到,程序已经暂停在断点位置了,而且在IDEA的左下角显示出了方法的调用栈。
我们还可以看到,在AspectJAutoProxyRegistrar类的registerBeanDefinitions()方法里面,首先调用了AopConfigUtils类的registerAspectJAnnotationAutoProxyCreatorIfNecessary()方法来注册registry,单看registerAspectJAnnotationAutoProxyCreatorIfNecessary()方法也不难理解,字面含义就是:如果需要的话,那么就注册一个AspectJAnnotationAutoProxyCreator组件。
接着,我们进入到AopConfigUtils类的registerAspectJAnnotationAutoProxyCreatorIfNecessary()方法中,如下所示
在AopConfigUtils类的registerAspectJAnnotationAutoProxyCreatorIfNecessary()方法中,直接调用了重载的registerAspectJAnnotationAutoProxyCreatorIfNecessary()方法。我们继续跟进代码,如下所示
可以看到在重载的registerAspectJAnnotationAutoProxyCreatorIfNecessary()方法中直接调用了registerOrEscalateApcAsRequired()方法,并且在registerOrEscalateApcAsRequired()方法中,传入了AnnotationAwareAspectJAutoProxyCreator.class对象。
我们继续跟进代码,如下所示。
我们可以看到,在registerOrEscalateApcAsRequired()方法中,接收到的Class对象的类型为org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator。
除此之外,我们还可以看到,在registerOrEscalateApcAsRequired()方法中会做一个判断,即首先判断registry(也就是IOC容器)是否包含名称为org.springframework.aop.config.internalAutoProxyCreator的bean
如果registry中包含名称为org.springframework.aop.config.internalAutoProxyCreator的bean,那么就进行相应的处理。从Spring的源码来看,就是将名称为org.springframework.aop.config.internalAutoProxyCreator的bean从容器中取出,并且判断cls对象的name值和apcDefinition的beanClassName值是否相等,若不相等,则获取apcDefinition和cls它俩的优先级,如果apcDefinition的优先级小于cls的优先级,那么将apcDefinition的beanClassName设置为cls的name值。相对来说,理解起来还是比较简单的。
由于我们这里是第一次运行程序,容器中应该还没有包含名称为org.springframework.aop.config.internalAutoProxyCreator的bean,所以此时并不会进入到if判断条件中。我们继续往下看代码,如下所示
这儿,会使用RootBeanDefinition来创建一个bean的定义信息(即beanDefinition),并且将org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator的Class对象作为参数传递进来
我们继续往下看代码,最终在AopConfigUtils类的registerOrEscalateApcAsRequired()方法中,会通过registry调用registerBeanDefinition()方法注册组件,如下所示
注册的组件的类型是org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator,组件的名字是org.springframework.aop.config.internalAutoProxyCreator。
我们继续往下看代码,最终会回到AspectJAutoProxyRegistrar类的registerBeanDefinitions()方法中。
接下来,我们继续往下看代码,即查看AspectJAutoProxyRegistrar类中的registerBeanDefinitions()方法的源码,如下所示
可以看到,通过AnnotationConfigUtils类的attributesFor()方法来获取@EnableAspectJAutoProxy注解的信息。接着,就是判断proxyTargetClass属性的值是否为true,若为true则调用AopConfigUtils类的forceAutoProxyCreatorToUseClassProxying()方法;继续判断exposeProxy属性的值是否为true,若为true则调用AopConfigUtils类的forceAutoProxyCreatorToExposeProxy()方法,其实就是暴露一些什么代理的这些bean,这个以后我们可以再说。
综上,向Spring的配置类上添加@EnableAspectJAutoProxy注解之后,会向IOC容器中注册AnnotationAwareAspectJAutoProxyCreator,翻译过来就叫注解装配模式的AspectJ切面自动代理创建器
这个AnnotationAwareAspectJAutoProxyCreator又是什么呢?别急,后面我们会核心研究它,现在我们只要知道在容器中注册了这样一个AnnotationAwareAspectJAutoProxyCreator组件就行了,至于注册的这个组件有什么功能呢?后面我们会继续研究它!其实,只要我们研究出来了,那么这个AOP的原理我们就彻底地知道了。
在研究它之前,我们来看下AnnotationAwareAspectJAutoProxyCreator类的结构图
可以看到,它继承了很多很多东东,我们简单梳理下AnnotationAwareAspectJAutoProxyCreato类的核心继承关系,如下所示
查看继承关系可以发现,此类实现了Aware与BeanPostProcessor接口,这两个接口都和Spring bean的初始化有关,由此可以推测此类的主要处理方法都来自于这两个接口中的实现方法
3、为AnnotationAwareAspectJAutoProxyCreator组件里面和后置处理器以及Aware接口有关的方法打上断点
在上一讲中,我们只是稍微分析了一下在配置类上添加@EnableAspectJAutoProxy注解之后,会向容器中注册了一个什么样的组件,因为咱们现在是要研究AOP的原理,而研究AOP的原理就得从@EnableAspectJAutoProxy注解入手研究。
我讲到这里,大家一定会恍然大悟,其实,要想知道AOP的原理,只需要搞清楚@EnableAspectJAutoProxy注解给容器中注册了什么组件,这个组件什么时候工作以及这个组件工作时候的功能是什么就行了,一旦把这个研究透了,那么AOP的原理我们就清楚了。
经过上一讲的分析研究,我们知道在配置类上添加@EnableAspectJAutoProxy注解之后,会向容器中注册了这样一个AnnotationAwareAspectJAutoProxyCreator组件。当然了,我们也简单梳理了一下它的核心继承关系,如下所示
通过以上继承关系,我们也知道了,它最终会实现两个接口,分别是:
- BeanPostProcessor:后置处理器,即在bean初始化完成前后做些事情
- BeanFactoryAware:自动注入BeanFactory
也就是说,AnnotationAwareAspectJAutoProxyCreator不仅是一个后置处理器,还是一个BeanFactoryAware接口的实现类。那么我们就来分析它作为后置处理器,到底做了哪些工作,以及它作为BeanFactoryAware接口的实现类,又做了哪些工作,只要把这个分析清楚,AOP的整个原理就差不多出来了
为AnnotationAwareAspectJAutoProxyCreator组件里面和后置处理器以及Aware接口有关的方法打上断点
接下来,我们就要为AnnotationAwareAspectJAutoProxyCreator这个组件里面和后置处理器以及Aware接口有关的方法都打上断点,看一下它们何时运行,以及都做了些什么事。
在打断点之前,我们还是得小心分析一下,因为AnnotationAwareAspectJAutoProxyCreator这个组件的继承关系还是蛮复杂的。由于是从AbstractAutoProxyCreator这个抽象类开始实现SmartInstantiationAwareBeanPostProcessor以及BeanFactoryAware这俩接口的,如果我们直接来AnnotationAwareAspectJAutoProxyCreator这个类里面找与Aware接口以及BeanPostProcessor接口有关的方法,是极有可能找不到的,所以我们还是得从它的最开始的父类(即AbstractAutoProxyCreator)开始分析。
我们找到该抽象类,并在里面查找与Aware接口以及BeanPostProcessor接口有关的方法,结果都是可以找到的。该抽象类中的setBeanFactory()方法就是与Aware接口有关的方法,因此我们将断点打在该方法上,如下图所示。
此外,我们还得找到该抽象类中与BeanPostProcessor接口有关的方法,即只要发现有与后置处理器相关的逻辑,就给所有与后置处理器有关的逻辑都打上断点。打的断点有两处,一处是在postProcessBeforeInstantiation()方法上,如下图所示
一处是在postProcessAfterInitialization()方法上,如下图所示。
接下来,我们再来看它的子类(即AbstractAdvisorAutoProxyCreator),从顶层开始一点一点往上分析。
在该抽象类中,我们只能找到一个与Aware接口有关的方法,即setBeanFactory()方法,虽然父类有setBeanFactory()方法,但是在这个子类里面已经把它重写了,因此最终调用的应该就是它。
大家注意,在重写的时候,在setBeanFactory()方法里面会调用一个initBeanFactory()方法。除此之外,该抽象类中就没有跟后置处理器有关的方法了。
接下来,我们就应该来看AspectJAwareAdvisorAutoProxyCreator这个类了,但由于这个类里面没有跟BeanPostProcessor接口有关的方法,所以我们就不必看这个类了,略过。
接下来,我们就要来看最顶层的类了,即AnnotationAwareAspectJAutoProxyCreator。查看该类时,发现有这样一个initBeanFactory()方法,我们在该方法上打上一个断点就好,如下图所示。
为什么在该类里面会有这个方法呢?因为我们在它的父类里面会调用setBeanFactory()方法,而在该方法里面又会调用initBeanFactory()方法,虽然父类里面有写,但是又被它的子类给重写了,所以说相当于父类中的setBeanFactory()方法还是得调用它。
那在该类中还有没有跟后置处理器有关的方法呢?没有了。
综上,我们通过简单的人工分析,为这个AnnotationAwareAspectJAutoProxyCreator类中有关后置处理器以及自动装配BeanFactoryAware接口的这些方法都打上了一些断点,接下来,我们就要来进行debug调试分析了。
不过在这之前,我们还得为MainConfigOfAOP配置类中的如下两个方法打上断点。
然后,我们就可以正式以debug模式来运行AOPTest测试类了,顺便分析一下整个流程
4、创建和注册AnnotationAwareAspectJAutoProxyCreator的过程
前言
在上一讲中,我们通过简单的人工分析,为AnnotationAwareAspectJAutoProxyCreator这个类中有关后置处理器以及自动装配BeanFactoryAware接口的那些方法都打上了一些断点,而且还为MainConfigOfAOP配置类中的两个方法打上了断点。
接下来,我们就以debug模式来运行AOPTest测试类,仔细分析一下整个流程。
创建和注册AnnotationAwareAspectJAutoProxyCreator的过程
以debug模式来运行AOPTest测试类之后,会先来到AbstractAdvisorAutoProxyCreator类的setBeanFactory()方法中,如下图所示。
你不禁就要问了,究竟是怎么来到这个setBeanFactory()方法中的啊?我们这就来分析一下,在左下角的方法调用栈中,仔细查找,就会在前面找到一个test01()方法,它其实就是AOPTest测试类中的测试方法,我们就从该方法开始分析,然后详细记录一下方法调用栈中整个的方法调用流程。
鼠标单击方法调用栈中的那个test01()方法,此时,我们会进入到AOPTest测试类中的test01()方法中,如下图所示。
可以看到这一步是传入主配置类来创建IOC容器,怎么创建的呢?我们点击方法调用栈中test01()方法上面的那个方法,就来到下面这个地方了。
可以看到,传入主配置类来创建IOC容器使用的是AnnotationConfigApplicationContext类的有参构造器,它具体分为下面三步:
- 首先使用无参构造器创建对象
- 再来把主配置类注册进来
- 最后调用refresh()方法刷新容器,刷新容器就是要把容器中的所有bean都创建出来,也就是说这就像初始化容器一样
接下来,我们来看看容器刷新是怎么做的?我们继续跟进方法调用栈,如下图所示,可以看到现在是定位到了AbstractApplicationContext抽象类的refresh()方法中。
其中,该refresh()方法中有一行非常重要的代码,那就是:
// Register bean processors that intercept bean creation.
this.registerBeanPostProcessors(beanFactory);
即注册bean的后置处理器。它的作用是什么呢?我们可以看一下它上面的注释,它就是用来方便拦截bean的创建的,那么这个后置处理器的注册逻辑又是什么样的呢?
我们继续跟进方法调用栈,可以看到现在是定位到了如下图所示的地方
我们继续跟进方法调用栈,如下图所示,可以看到现在是定位到PostProcessorRegistrationDelegate类的registerBeanPostProcessors()方法中了
以上registerBeanPostProcessors()方法中的代码还是蛮多的,我怕童鞋们看不大清,所以又截了一张图,如下所示,这下看得够清楚了吧!
哇塞,这个方法真的是好长啊!有的同学可能看得头都大了,但是没关系,我来帮大家分析一下,到底是怎么注册bean的后置处理器的。
-
先按照类型拿到IOC容器中所有需要创建的后置处理器,即先获取IOC容器中已经定义了的需要创建对象的所有BeanPostProcessor。这可以从如下这行代码中得知
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
你可能要问了,为什么IOC容器中会有一些已定义的BeanPostProcessor呢?这是因为在前面创建IOC容器时,需要先传入配置类,而我们在解析配置类的时候,由于这个配置类里面有一个@EnableAspectJAutoProxy注解,对于该注解,我们之前也说过,它会为我们容器中注册一个AnnotationAwareAspectJAutoProxyCreator(后置处理器),这还仅仅是这个@EnableAspectJAutoProxy注解做的事,除此之外,容器中还有一些默认的后置处理器的定义。
所以,程序运行到这,容器中已经有一些我们将要用的后置处理器了,只不过现在还没创建对象,都只是一些定义,也就是说容器中有哪些后置处理器。
-
继续往下看这个registerBeanPostProcessors()方法,可以看到它里面还有其他的逻辑,如下所示:
beanFactory.addBeanPostProcessor(new PostProcessorRegistrationDelegate.BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));
说的是给beanFactory中额外还加了一些其他的BeanPostProcessor,也就是说给容器中加别的BeanPostProcessor。
-
继续往下看这个registerBeanPostProcessors()方法,发现它里面还有这样的注释,如下所示
// Separate between BeanPostProcessors that implement PriorityOrdered, // Ordered, and the rest. List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList();
说的是分离这些BeanPostProcessor,看哪些是实现了PriorityOrdered接口的,哪些又是实现了Ordered接口的,包括哪些是原生的没有实现什么接口的。所以,在这儿,对这些BeanPostProcessor还做了一些处理,所做的处理看以下代码便一目了然
String ppName; BeanPostProcessor pp; for(int var10 = 0; var10 < var9; ++var10) { ppName = var8[var10]; if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { pp = (BeanPostProcessor)beanFactory.getBean(ppName, BeanPostProcessor.class); priorityOrderedPostProcessors.add(pp); if (pp instanceof MergedBeanDefinitionPostProcessor) { internalPostProcessors.add(pp); } } else if (beanFactory.isTypeMatch(ppName, Ordered.class)) { orderedPostProcessorNames.add(ppName); } else { nonOrderedPostProcessorNames.add(ppName); } }
拿到IOC容器中所有这些BeanPostProcessor之后,是怎么处理的呢?它是来看我们这个BeanPostProcessor是不是实现了PriorityOrdered接口,我们不妨看一下PriorityOrdered接口的源码,如下图所示
可以看到该接口其实是Ordered接口旗下的,也就是说它继承了Ordered接口。进一步说明,IOC容器中的那些BeanPostProcessor是有优先级排序的。
好了,现在我们知道了这样一个结论,那就是:IOC容器中的那些BeanPostProcessor可以实现PriorityOrdered以及Ordered这些接口来定义它们工作的优先级,即谁先前谁先后。
回到代码中,就不难看到,它是在这儿将这些BeanPostProcessor做了一下划分,如果BeanPostProcessor实现了PriorityOrdered接口,那么就将其保存在名为priorityOrderedPostProcessors的List集合中,并且要是该BeanPostProcessor还是MergedBeanDefinitionPostProcessor这种类型的,则还得将其保存在名为internalPostProcessors的List集合中。
-
继续往下看这个registerBeanPostProcessors()方法,主要是看其中的注释,不难发现有以下三步:
- 优先注册实现了PriorityOrdered接口的BeanPostProcessor
- 再给容器中注册实现了Ordered接口的BeanPostProcessor
- 最后再注册没实现优先级接口的BeanPostProcessor
那么,所谓的注册BeanPostProcessor又是什么呢?我们还是来到程序停留的地方,为啥子程序会停留在这儿呢?因为咱们现在即将要创建的名称为internalAutoProxyCreator的组件(其实它就是我们之前经常讲的AnnotationAwareAspectJAutoProxyCreator)实现了Ordered接口,这只要查看AnnotationAwareAspectJAutoProxyCreator类的源码便知,一级一级地往上查
再次回到程序停留的地方,如下图所示。
可以看到,是先拿到要注册的BeanPostProcessor的名字,然后再从beanFactory中来获取。
接下来,我们就要获取相应名字的BeanPostProcessor了,怎么获取呢?继续跟进方法调用栈,如下图所示,可以看到现在是定位到了AbstractBeanFactory抽象类的getBean()方法中。
我们继续跟进方法调用栈,如下图所示,可以看到现在是定位到了AbstractBeanFactory抽象类的doGetBean()方法中
这个方法特别特别的长,这儿我就不再详细分析它了,只须关注程序停留的这行代码即可。这行代码的意思是调用getSingleton()方法来获取单实例的bean,但是呢,IOC容器中第一次并不会有这个bean,所以第一次获取它肯定是会有问题的。
我们继续跟进方法调用栈,如下图所示,可以看到现在是定位到了DefaultSingletonBeanRegistry类的getSingleton()方法中。
也就是说如果从IOC容器中第一次获取单实例的bean出现问题,也即获取不到时,那么就会调用singletonFactory的getObject()方法。
我们继续跟进方法调用栈,如下图所示,可以看到现在又定位到了AbstractBeanFactory抽象类的doGetBean()方法中。
可以发现,现在就是来创建bean的,也就是说如果获取不到那么就创建bean。咱们现在就是需要注册BeanPostProcessor,说白了,实际上就是创建BeanPostProcessor对象,然后保存在容器中
那么接下来,我们就来看看是如何创建出名称为internalAutoProxyCreator的BeanPostProcessor的,它的类型其实就是我们之前经常说的AnnotationAwareAspectJAutoProxyCreator。我们就以它为例,来看看它这个对象是怎么创建出来的。
我们继续跟进方法调用栈,如下图所示,可以看到现在是定位到了AbstractAutowireCapableBeanFactory抽象类的createBean()方法中。
接着再跟进方法调用栈,如下图所示,可以看到现在是定位到了AbstractAutowireCapableBeanFactory抽象类的doCreateBean()方法中。
程序停留在这儿,就是在初始化bean实例,说明bean实例已经创建好了,如果你要不信的话,那么可以往前翻阅该doCreateBean()方法,这时你应该会看到一个createBeanInstance()方法,说的就是bean实例的创建。创建的是哪个bean实例呢?就是名称为internalAutoProxyCreator的实例,该实例的类型就是我们之前经常说的AnnotationAwareAspectJAutoProxyCreator,即创建这个类型的实例。创建好了之后,就在程序停留的地方进行初始化
所以,整个的过程就应该是下面这个样子的:
- 首先创建bean的实例
- 然后给bean的各种属性赋值(即调用populateBean()方法)
- 接着初始化bean(即调用initializeBean()方法),这个初始化bean其实特别地重要,因为我们这个后置处理器就是在bean初始化的前后进行工作的。
接下来,我们就来看看这个bean的实例是如何初始化的。继续跟进方法调用栈,如下图所示,可以看到现在是定位到了AbstractAutowireCapableBeanFactory抽象类的initializeBean()方法中。
我就在这儿为大家详细分析一下初始化bean的流程。
-
首先我们进入invokeAwareMethods()这个方法里面看一下,如下图所示。
其实,这个方法是来判断我们这个bean对象是不是Aware接口的,如果是,并且它还是BeanNameAware、BeanClassLoaderAware以及BeanFactoryAware这几个Aware接口中的其中一个,那么就调用相关的Aware接口方法,即处理Aware接口的方法回调
现在当前的这个bean叫internalAutoProxyCreator,并且这个bean对象已经被创建出来了,创建出来的这个bean对象之前我们也分析过,它是有实现BeanFactoryAware接口的,故而会调用相关的Aware接口方法,这也是程序为什么会停留在invokeAwareMethods()这个方法的原因
-
还是回到AbstractAutowireCapableBeanFactory抽象类的initializeBean()方法中,即程序停留的地方。如果invokeAwareMethods()这个方法执行完了以后,那么后续又会发生什么呢?
往下翻阅initializeBean()方法,会发现有一个叫applyBeanPostProcessorsBeforeInitialization的方法,如下图所示
这个方法调用完以后,会返回一个被包装的bean。
该方法的意思其实就是应用后置处理器的postProcessBeforeInitialization()方法。我们可以进入该方法中去看一看,到底是怎么应用后置处理器的postProcessBeforeInitialization()方法的?
可以看到,它是拿到所有的后置处理器,然后再调用后置处理器的postProcessBeforeInitialization()方法,也就是说bean初始化之前后置处理器的调用在这儿。
-
还是回到程序停留的地方,继续往下翻阅initializeBean()方法,你会发现还有一个叫invokeInitMethods的方法,即执行自定义的初始化方法。
这个自定义的初始化方法呢,你可以用@bean注解来定义,指定一下初始化方法是什么,销毁方法又是什么,这个我们之前都说过了。
-
自定义的初始化方法执行完以后,又有一个叫applyBeanPostProcessorsAfterInitialization的方法,该方法的意思其实就是应用后置处理器的postProcessAfterInitialization()方法。我们可以进入该方法中去看一看,到底是怎么应用后置处理器的postProcessAfterInitialization()方法的?
依旧是拿到所有的后置处理器,然后再调用后置处理器的postProcessAfterInitialization()方法。
所以,后置处理器的这两个postProcessBeforeInitialization()与postProcessAfterInitialization()方法前后的执行,就是在这块体现的。我们在这儿也清楚地看到了。
接下来,我们还是回到程序停留的地方,即下面这行代码处。
this.invokeAwareMethods(beanName, bean);
调用initializeBean()方法初始化bean的时候,还得执行那些Aware接口的方法,那到底怎么执行呢?正好我们知道,当前的这个bean它确实是实现了BeanFactoryAware接口。因此我们继续跟进方法调用栈,如下图所示,可以看到现在是定位到了AbstractAutowireCapableBeanFactory抽象类的invokeAwareMethods()方法中。
再继续跟进方法调用栈,如下图所示,可以看到现在是定位到了AbstractAdvisorAutoProxyCreator抽象类的setBeanFactory()方法中。
可以看到现在调用的是AbstractAdvisorAutoProxyCreator抽象类中的setBeanFactory()方法。我们要创建的是AnnotationAwareAspectJAutoProxyCreator对象,但是调用的却是它父类的setBeanFactory()方法。至此,咱们一步一步地分析,就分析到这儿了,可以说是相当地不简单了,不知你看清了没有。
接下来,按下F6
快捷键让程序往下运行,父类的setBeanFactory()方法便会被调用,再按下F6
快捷键让程序往下运行,一直让程序运行到如下图所示的这行代码处
可以看到父类的setBeanFactory()方法被调用完了。然后按下F6
快捷键继续让程序往下运行,这时会运行到如下这行代码处
该initBeanFactory()方法就是用来初始化BeanFactory的。我们按下F5快捷键进入到当前方法内部,如下图所示,可以看到调用到了AnnotationAwareAspectJAutoProxyCreator这个类的initBeanFactory()方法中了,即调到了我们要给容器中创建的AspectJ自动代理创建器的initBeanFactory()方法中。
接着按下F6快捷键继续让程序往下运行,运行完该方法,如下图所示。
可以看到这个initBeanFactory()方法创建了两个东西,一个叫ReflectiveAspectJAdvisorFactory,还有一个叫BeanFactoryAspectJAdvisorsBuilderAdapter,它相当于把之前创建的aspectJAdvisorFactory以及beanFactory重新包装了一下,就只是这样。
至此,整个这么一个流程下来以后,咱们的这个BeanPostProcessor,我们是以AnnotationAwareAspectJAutoProxyCreator(就是@EnableAspectJAutoProxy这个注解核心导入的BeanPostProcessor)为例来讲解的,就创建成功了。并且还调用了它的initBeanFactory()方法得到了一些什么aspectJAdvisorFactory和aspectJAdvisorsBuilder,这两个东东大家知道一下就行了。至此,整个initBeanFactory()方法我们就说完了,也就是说我们整个的后置处理器的注册以及创建过程就说完了。
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
你应该知道,我们是以AnnotationAwareAspectJAutoProxyCreator为例来讲解的,现在你也应该知道整个后置处理器的创建以及注册流程了。
此刻,后置处理器已经在容器中注册进来了。所谓的注册又是什么呢?接下来,我们可以再来看一下,按下F6快捷键继续让程序往下运行,一直让程序运行到AbstractAutowireCapableBeanFactory抽象类的initializeBean()方法中的如下这行代码处。
紧接着就是应用各种什么applyBeanPostProcessorsBeforeInitialization()方法或者applyBeanPostProcessorsAfterInitialization()方法了,我们继续按下F6快捷键让程序往下运行,一直让程序运行到如下图所示的这行代码处。
可以看到咱们要创建的后置处理器(即AnnotationAwareAspectJAutoProxyCreator)总算是创建完了。
继续按下F6
快捷键让程序往下运行,一直让程序运行到如下图所示的这行代码处。
上面这行代码的意思是说,后置处理器创建完以后会添加到我们已创建的那个bean集合里面
继续按下F6
快捷键让程序往下运行,一直让程序运行到如下图所示的这行代码处
可以看到,咱们这个BeanPostProcessor(即AnnotationAwareAspectJAutoProxyCreator)创建完了以后,会放进了一个叫什么internalPostProcessors的这个集合里面。
继续按下F6
快捷键让程序往下运行,会调用sortPostProcessors()方法按照优先级给这些后置处理器们排一个序,程序再往下运行,就会调用到registerBeanPostProcessors()方法了,进到该方法中去看一下。
现在你该知道所谓的注册是什么了吧😂!就是拿到所有的BeanPostProcessor,然后调用beanFactory的addBeanPostProcessor()方法将BeanPostProcessor注册到BeanFactory中。
至此,整个BeanPostProcessor的创建以及注册过程,我们也就说完了,是不是很复杂,不过你只要看了我写的这篇文章,相信你一定搞清楚了!