Spring版本:
<version>5.2.1.RELEASE</version>
目录
上一篇:21-Spring源码解析——IOC容器创建与Bean生命周期总结
截至到本篇文章,我终于把IOC写完啦!现在开启Spring的第二个百宝箱:AOP
一、AOP概览
我们知道,使用 面向对象编程(OOP) 有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共行为的时候,例如:日志、安全检测等,我们只有在每个对象里引用公共行为,这样程序中就产生了大量的重复代码,程序就不便于维护了,所以就有了一个对面向对象编程的补充,即 面向切面编程(AOP) ,AOP所关注的方向是横向的,不同于OOP的纵向。
Spring 2.0采用@AspectJ注解对POJO进行标注,从而定义了包含切点信息和增强横切逻辑的切面。Spring 2.0 将这个切面织入到匹配的目标Bean中。
下面,我们先来直观地浏览一下Spring中AOP的简单示例。
1. 例子
我们这个例子比较纯粹,只有4个类
- 创建用于拦截的计算器类
- 切面类(用于实现
AOP功能) - 配置类
- 测试类
1.1 创建用于拦截的Bean
在实际工作中,这个Bean可能是满足业务需要的核心逻辑,比如满足计算需要的计算器类。这个类包含一个用于做除法运算的除法div()方法,但是我们想在div()方法前后增加日志来跟踪调试,如果这个时候我们直接修改源码并不符合面向对象的设计特点,而且随意改动原有的代码会造成一定的风险,还好Spring给我们提供了方案:在不改变计算器类的情况下,完成我们的增加日志的需求。
import org.springframework.stereotype.Component;
@Component
public class MathCalculator {
public int div(int i, int j) {
return i / j;
}
@Override
public String toString() {
return "MathCalculator{}";
}
}
1.2 切面类
package com.fj.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
// @Aspect 告诉Spring这是一个切面类
@Component
@Aspect
public class LogAspects {
// 抽取公共的切入点表达式
@Pointcut("execution(public int com.fj.aop.MathCalculator.div(int, int))")
public void pointCut(){};
// MathCalculator类的所有方法 *,方法的参数是任意多的 ..
@Before("pointCut()")
public void logStart(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
System.out.println(" " + joinPoint.getSignature().getName()+ " 的【logStart】方法: 除法开始运行,参数列表是,{"+ Arrays.asList(args) +"}");
}
@After("pointCut()")
public void logEnd(JoinPoint joinPoint) {
System.out.println(" " + joinPoint.getSignature().getName()+ " 的【logEnd】方法: 除法结束。");
}
@AfterReturning(value = "pointCut()", returning = "result")
public void logReturn(Object result) {
System.out.println(" 【logReturn】方法: 运行结果:{"+ result +"}");
}
@AfterThrowing(value = "pointCut()", throwing = "exception")
public void logException(JoinPoint joinPoint, Exception exception){
System.out.println(" " + joinPoint.getSignature().getName()+ " 的 【logException】 方法 除法出现异常,异常信息:{"+exception+"}");
}
}
这个切面类包含5个方法:一个用于抽取公共的切入点表达式方法。剩下四个为:前置通知(@Before)、后置通知(@After)、返回通知(@AfterReturning)和异常通知(@AfterThrowing)方法。
1.3 配置类
@ComponentScan("com.fj.aop")
@Configuration
@EnableAspectJAutoProxy
public class MainConfig_AOP {
}
这个配置类很简单,主要的作用有两个:
- 包扫描:将 创建用于拦截的
Bean类和切面类扫描到Spring容器中 - 开启基于注解的aop模式:
@EnableAspectJAutoProxy
1.4 测试类
public class AOPTest {
@Test
public void test01() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig_AOP.class);
Object calculator = applicationContext.getBean(MathCalculator.class);
Object logAspect = applicationContext.getBean(LogAspects.class);
System.out.println(logAspect.getClass().getName());
System.out.println(calculator.getClass().getName());
System.out.println(calculator instanceof MathCalculator);
((MathCalculator) calculator).div(3,2);
}
}
测试类做了4个事情:
- 输出从容器中获取到的
LogAspects类型的Bean名字。 - 输出从容器中获取到的
MathCalculator类型的Bean名字。(这一步输出的结果很重要) - 判断从容器中获取到的
MathCalculator类型的Bean是否还是MathCalculator类型 - 调用获取到的
MathCalculator类型的Bean的div()方法
1.5 测试结果

我看可以看到上面的执行结果:
-
输出从容器中获取到的
LogAspects类型的Bean名字。Bean的名字是原来的LogAspects
-
输出从容器中获取到的
MathCalculator类型的Bean名字。Bean的名字已经不是原来的MathCalculator,而是MathCalculator$$EnhancerBySpringCGLIB$$44e84cdd,为什么?为什么LogAspects得到的就是原来的LogAspects名字,而MathCalculator得到的就不是呢?之后详细介绍,这一步很重要!
-
判断从容器中获取到的
MathCalculator类型的Bean是否还是MathCalculator类型- 我们使用
Object类型的对象来得到applicationContext.getBean(MathCalculator.class);的返回值,返回虽然已经不是我们原来的MathCalculator,但是还是MathCalculator类型的对象,所以这一步返回true
- 我们使用
-
调用获取到的
MathCalculator类型的Bean的div()方法- 因为方法正常返回没有出现异常,所以会调用切面类的
logStart方法、logEnd方法和logReturn方法
- 因为方法正常返回没有出现异常,所以会调用切面类的
从上面的执行结果可以看出,Spring实现了对MathCalculator类的div方法的增强,且增强的逻辑独立于MathCalculator类之外。
那么,Spring是如何实现AOP功能的呢?
那我们就得从源头开始找,首先我们知道,Spring创建容器的时候,是需要解析配置类(带有@Configuration注解的类)的吧,我们项目中所有的类都是通过这个配置类来让Spring发现且管理的。其次,记得不记得在本篇文章的例子中配置类上面增加了一个注解@EnableAspectJAutoProxy,对,就是它,这个注解是做什么的呢?就是开启基于注解的AOP模式。所以要分析AOP原理,首先就逮住@EnableAspectJAutoProxy开始分析。
二、 @EnableAspectJAutoProxy注解
我们看一下这个注解的源码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}
看它上面有一个非常重要的注解@Import(AspectJAutoProxyRegistrar.class),引入AspectJAutoProxyRegistrar类。我们先看一下这个类结构:
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
这个类实现了ImportBeanDefinitionRegistrar接口,且就一个方法:registerBeanDefinitions,从方法名字也可以看出来这个方法的作用是:注册BeanDefinition。具体registerBeanDefinitions方法是怎么注册BeanDefinition的这里先不分析。毕竟程序还没有debug到这里,后面会一步一步走到这里的时候会详细分析。
现在,我们就从初始化容器开始一步一步debug看一下到底Spring是怎么实现AOP功能的。
三、解析@EnableAspectJAutoProxy注解
我们从下面这行代码开始分析,不求快但求精致。
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig_AOP.class);
我们知道,在Spring创建容器的时候会从解析配置类开始管理我们项目中的类。那我们就从解析配置类开始。 解析配置类是在:refresh方法调用的第五个方法invokeBeanFactoryPostProcessors方法中进行。这里我只抽取与该功能有关的部分代码进行讲解,与该功能无关的方法暂时省略。(若有兴趣了解invokeBeanFactoryPostProcessors方法的同学参见文章10-Spring源码解析之refresh(4) )
Spring首先创建解析配置类的类ConfigurationClassPostProcessor,然后开始利用ConfigurationClassPostProcessor解析配置类。

我们可以看到:
- 当前
postProcessorNames数组只有一个值,即ConfigurationClassPostProcessor类型的对象,我们先对它调用getBean方法,当前容器中还没有这个Bean,所以调用getBean方法后Spring会创建一个ConfigurationClassPostProcessor类型的对象。 - 然后利用
ConfigurationClassPostProcessor对象去解析我们的配置类(MainConfig_AOP)
我们进入invokeBeanDefinitionRegistryPostProcessors方法看它是如何解析的配置类:

因为这里只有一个BeanDefinitionRegistryPostProcessor类型的PostProcessor,即解析配置类的类:ConfigurationClassPostProcessor。继续跟踪postProcessBeanDefinitionRegistry。

上图调用的processConfigBeanDefinitions方法的实现有点多,所以就截取与解析@EnableAspectJAutoProxy注解有关的步骤了。

注意到目前为止只有上面的图片我标注了图一,是因为,一会解析完配置类后还要回到这里!
processConfigBeanDefinitions方法调用ConfigurationClassParser类型的对象parse的parse方法来解析配置类mainConfig_AOP。 要开始解析咯,关注@Import是怎么解析的咯!


进入ConfigurationClassParser类的processConfigurationClass方法
遇到doXXX了!说明Spring要开始真正解析了。进入doProcessConfigurationClass方法,找到解析@Import的地方。

我们要注意一下右下角configClass的importBeanDefinitionRegistrars属性,在解析配置类mainConfig_AOP之前,这个属性里面什么都没有。
进入processImports方法

我们可以看到for循环中的值在本例子中只有一个,即AspectJAutoProxyRegistrar。在本篇文章二、节中我们已经知道AspectJAutoProxyRegistrar类实现了ImportBeanDefinitionRegistrar接口,因此会进入else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class))语句块。这个语句块的作用为:创建AspectJAutoProxyRegistrar类对象,然后将它加入到configClass的importBeanDefinitionRegistrar属性中。
因此执行完这段代码后,我们再看configClass。

现在已经解析完了配置类mainConfig_AOP中的@Import注解,并将其对应的类添加到configClass的importBeanDefinitionRegistrar属性中了。那我们就回去看看解析完parse之后做了什么,即我们回到图一。

我们注意,在parser.parse(candidates)方法下面,我标注黄色框的区域,Spring创建了一个ConfigurationClassBeanDefinitionReader类型的对象,并将这个对象赋值给this.reader。this.reader是什么呢?马上去源码里看了一下粘了出来。

接着调用ConfigurationClassBeanDefinitionReader的loadBeanDefinitions方法。将configClasses传递进去。首先,我们得知道configClasses里面存储的东西,它里面存储的是从配置类mainConfig_AOP解析出来的类信息。那我们看一下当前项目中configClasses里面的值。

configClasses里面包含三个值:
LogAspects类MathCalculator类MainConfig_AOP类
其中LogAspects类和MathCalculator类是通过解析@ConponentScan包扫描得到的。MainConfig_AOP类中有一个importBeanDefinitionRegistrars。
接下来我们看一下this.reader.loadBeanDefinitions(configClasses);方法的具体实现

这里是一个for循环,我们主要关注当configClass为mainConfig_AOP时,执行这个方法的具体实现。

我将前面与AOP功能无关的条件语句折叠起来了,我们主要看最后一条红色框住的地方:注册configClass的importBeanDefinitionRegistrars属性值为BeanDefintion。

实际上就是注册AspectJAutoProxyRegistrar为BeanDefinition。
从上图可以看出来,为了注册AspectJAutoProxyRegistrar为BeanDefinition,程序走到了ImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法,但是由于AspectJAutoProxyRegistrar实现了该接口并重写了registerBeanDefinitions方法,因此下一步程序就走到了AspectJAutoProxyRegistrar类的registerBeanDefinitions方法中。即在本篇文章二、节贴出来的第二段代码。下面再重新贴出来一下。

到达了AspectJAutoProxyRegistrar类的registerBeanDefinitions方法才算开始与AOP功能有了进一步的深入了解。在registerBeanDefinitions方法中首先调用了AopConfigUtils类的registerAspectJAnnotationAutoProxyCreatorIfNecessary方法,从方法名中就可以看出来该方法的功能是:如果有必要就注册一个AspectJAnnotationAutoProxyCreatorI类。实际上到这一步一定会在Spring中注册一个AspectJAnnotationAutoProxyCreator。注册完AspectJAnnotationAutoProxyCreatorI,解析@EnableAspectJAutoProxy注解的工作配置类就算是做完了。
那么现在的问题就转变为了:
- 为什么在配置类中写了
@EnableAspectJAutoProxy注解,Spring就为我们在容器中注册了一个AspectJAnnotationAutoProxyCreator类 AspectJAnnotationAutoProxyCreator类是什么,它的类结构是什么样子的- 实现
AOP功能与AspectJAnnotationAutoProxyCreator类有什么关系
那么我们就带着这3个问题来看下一篇文章。
来源:CSDN
作者:想当厨子的程序媛
链接:https://blog.csdn.net/xiaojie_570/article/details/104791656