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