说在前头: 笔者本人为大三在读学生,书写文章的目的是为了对自己掌握的知识和技术进行一定的记录,同时乐于与大家一起分享,因本人资历尚浅,发布的文章难免存在一些错漏之处,还请阅读此文章的大牛们见谅与斧正。若在阅读时有任何的问题,也可通过评论提出,本人将根据自身能力对问题进行一定的解答。
手撸Spring系列是笔者本人首次尝试的、较为规范的系列博客,将会围绕Spring框架分为IOC/DI 思想
、Spring MVC
、AOP 思想
、Spring JDBC
四个模块,并且每个模块都会分为理论篇
、源码篇
、实战篇
三个篇章进行讲解(大约12篇文章左右的篇幅)。从原理出发,深入浅出,一步步接触Spring源码并手把手带领大家一起写一个 迷你版的Spring框架 ,促进大家进一步了解Spring的本质!
由于源码篇涉及到源码的阅读,可能有小伙伴没有成功构建好Spring源码的阅读环境,笔者强烈建议:想要真正了解Spring,一定要构建好源码的阅读环境再进行研究,具体构建过程可查看笔者此前的博客:《如何构建Spring5源码阅读环境》
前言
从今天的博客开始,我们将开始探究Spring AOP 的源码~!!
由于AOP的理论和概念理解起来会比较困难(当然,你是大佬除外),笔者刚接触时也在了AOP的理论知识中困了许久。但如果试着去完成了一个Spring AOP的小Demo
后,一般思路就会豁然开朗。因此,在此次的理论篇中,除了讲解Spring AOP的理论知识外,还会附带一个小的实操案例~~!!
一、什么是AOP
我们先来看看百度百科是如何解释AOP的
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP
为Aspect Oriented Programming
的缩写,直译为“面向切面编程”。在初学Java时我们开始接触了OOP
面向对象编程,而学习Spring IOC 后我们又学习了OOB
面向Bean编程。此时又多了一个AOP
面向切面编程,我们又该如何来理解所谓面向切面的编程呢?
首先希望大家要搞懂一个最重要的点,AOP是为了 增强
一个方法而存在的,它并不会左右方法具体的 业务逻辑 ,通过AOP的切点织入的代码都是处理 非业务逻辑 的任务。如 写日志
等操作是不参与具体的业务逻辑的,日志写或者是不写,并不会左右你的程序是否能够正常运行,只是出现异常时的排查工作会比较麻烦而已,像这种写日志操作就是非业务逻辑的代码。而像这种代码在每个方法中几乎都是重复的,复制黏贴式的代码会给程序的维护带来不便,此时AOP将这些业务需求和系统需求分开的优势就展现出来了!!
二、AOP重要的概念
AOP中有几个重要的概念,分别是:切面Aspect
、通知Advice
、切点Pointcut
、连接点(JoinPonit)
、目标对象(Target)
、织入
。
- 切点: 切点就是具体需要去
切
的地方(具体需要增强的方法) - 通知: 通知包含两要素:①通知的时间;②通知的信息(具体要做什么事情)
- 切面: = 切点 + 通知(去哪里?什么时候?去做些什么?)
- 连接点: 连接点是在应用执行过程中能够插入切面的一个点,这个点可以是调用方法时、抛出以异常时、甚至修改一个字段时
- 目标对象: 需要被代理的类,如IndexService
- 织入: 把一个切面应用到真实对象上面的过程,就叫做织入(知道时间、地点、任务后,最后就是执行啦)
其中通知还分为前置通知
、后置通知
、异常通知
、最终通知
、环绕通知
。
- 前置通知(Before Advice): 方法执行前执行
- 后置返回通知(After Return Advice): 方法返回之前执行,可以对方法的返回值进行修改,在此之前如果出现异常则不会执行
- 后置通知(After Advice): 方法返回之后执行,不可以对方法的返回值进行修改,即使出现异常也会执行
- 异常通知(After Throwing Advice): 方法执行异常后执行
- 环绕通知(Around Advice): 环绕通知可以看成是前四个通知的集合,通过它就可以完成前四个通知的任务,并且环绕通知会比前置通知先执行。
为了让大家能够进一步的理解这些概念,笔者将带领着各位读者朋友们来写一个小demo~!!
三、.写一个小Demo
ApplicationConfig
首先我们需要开启AOP功能,这里我们使用注解的方式,在配置类上使用注解@EnableAspectJAutoProxy
即可
@EnableAspectJAutoProxy
@Configuration
public class ApplicationConfig {}
LogAspect
接着写一个日志切面类,里面定义了切点以及前置通知、后置通知、异常通知、最终通知。其中,@Pointcut
的匹配规则,还不了解的读者朋友们可以通过这篇博客进行了解:Spring AOP 切点匹配规则
package com.example.demo.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* <p>日志AOP配置类</p>
* @author Bosen
* @date 2021/9/17 15:37
*/
@Component
@Aspect
public class LogAspect {
/**
* <p>配置切面</p>
*/
@Pointcut("execution(public * com.example.demo.service.*.*(..))")
public void logPointcut(){}
/**
* <p>前置通知</p>
*/
@Before("logPointcut()")
public void logBefore() {
System.out.println("This is LogAspect before");
}
/**
* <p>后置返回通知</p>
*/
@AfterReturning("logPointcut()")
public void logAfterReturning() {
System.out.println("This is LogAspect AfterReturning");
}
/**
* <p>异常通知</p>
*/
@AfterThrowing("logPointcut()")
public void logAfterThrowing() {
System.out.println("This is LogAspect AfterThrowing");
}
/**
* <p>后置通知</p>
*/
@After("logPointcut()")
public void logAfter() {
System.out.println("This is LogAspect After");
}
}
IndexService
最后编写一个可以用于调用的service层方法echo
public class IndexService {
public void echo() {
System.out.println("This is IndexService echo");
}
}
调用echo方法
@SpringBootTest
class DemoApplicationTests {
@Autowired
IndexService service;
@Test
void contextLoads() {
service.echo();
}
}
执行后的结果如下:
由于echo
方法并没有异常抛出,因此不会执行异常通知,但如果出现异常,则后置返回通知将不会执行,转而执行异常通知,但后置通知不会受影响。
体验过上面四个通知(前置通知、后置返回通知、异常通知、后置通知)后,我们再来看看环绕通知,我们可以将环绕通知看成是前四个通知的集合。
环绕通知Around Advice
我们修改一下日志切面类LogAsoect
@Component
@Aspect
public class LogAspect {
/**
* <p>配置切面</p>
*/
@Pointcut("execution(public * com.example.demo.service.*.*(..))")
public void logPointcut(){}
/**
* <p>环绕通知</p>
*/
@Around("logPointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) {
// 得到方法执行所需的参数
Object[] args = joinPoint.getArgs();
Object proceed = null;
try {
System.out.println("前置通知Before");
// 明确调用业务层方法
proceed = joinPoint.proceed(args);
System.out.println("后置返回通知AfterReturning");
}catch (Throwable throwable){
System.out.println("异常通知AfterThrowing");
throwable.printStackTrace();
} finally {
System.out.println("后置通知After");
}
return proceed;
}
}
再次调用echo结果如下:
通过上面的调用,我们发现其实环绕通知就可以独自完成前置、后置返回、异常、后置通知。
到了这里,相信各位读者朋友们已经对切面、切点、通知之间的关系已经有了一个比较清晰的理解~!!有了基础后,讲起Spring AOP的执行流程将会变得特别容易了~!!
四、Spring AOP执行流程
转自:https://blog.csdn.net/yxh13521338301/article/details/105224144
@EnableAspectJAutoProxy
开启aop- 底层是将
AspectJAutoProxyRegistrar
注入spring容器 - AspectJAutoProxyRegistrar通过AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry)的底层
registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source)
将AnnotationAwareAspectJAutoProxyCreator
注入spring容器 - AnnotationAwareAspectJAutoProxyCreator是BeanPostProcessor的子类,也是AOP的入口
- AnnotationAwareAspectJAutoProxyCreator的上级中
AbstractAutoProxyCreator
为AOP的核心类 - AbstractAutoProxyCreator重新了后置处理方法
postProcessAfterInitialization
,其中通过wrapIfNecessary
创建代理 - Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); - 层层包装调用,进入到
DefaultAopProxyFactory
的createAopProxy
方法创建代理 - 创建代理时判断目标对象是否实现接口,是则使用
JDK
的动态代理,否则使用CGLIB
的动态代理(CGLIB可以适用于有接口或无接口的类,但JDK动态代理只可以用于实现了接口的类) - 在JDK的动态代理的类JdkDynamicAopProxy中,可看到其中invoke方法实现代理功能
- List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); 获取aop的所有通知
- 使用责任链模式执行所有通知,执行目标方法
- 当我们在调用
getBean
去实例化springbean时,安装springbean的生命周期,在执行完bean自定义的init方法后,进入后置处理
这时就会创建代理类,当bean执行方法时,进入代理类的invoke,执行所有通知,再执行目标方法,实现代理功能