[源码系列:手写spring] AOP第一节:切点表达式
在本专栏之前的文章中已经带大家熟悉了Spirng中核心概念IOC的原理以及手写了核心代码,接下来将继续介绍Spring中另一核心概念AOP。
AOP即切面编程是Spring框架中的一个关键概念,它允许开发者在应用程序中优雅地处理横切关注点,如日志记录、性能监控和事务管理。在切面编程中,切点表达式是一项关键技术,它定义了在何处应用切面的逻辑。本章将深入探讨Spring切点表达式的实现原理,为读者提供对这一重要概念的深刻理解。
1.AOP案例
1.1 案例背景
假设我们有一个在线商城的Web应用,用户可以浏览商品、下单购买商品等。我们希望记录每个HTTP请求的开始时间、结束时间以及执行时间,以便监控应用的性能并快速定位潜在的问题。
1.2 AOP解决方案
我们可以使用Spring AOP和切点表达式来实现这个日志记录功能。以下是实现步骤:
1. 创建一个切面类:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class RequestLoggingAspect {
private long startTime;
@Before("execution(* com.example.controller.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
startTime = System.currentTimeMillis();
System.out.println("Request received for: " + joinPoint.getSignature().toShortString());
}
@After("execution(* com.example.controller.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
System.out.println("Request completed for: " + joinPoint.getSignature().toShortString());
System.out.println("Execution Time: " + executionTime + "ms");
}
}
上述代码定义了一个切面类 RequestLoggingAspect,其中包含两个通知方法 logBefore 和 logAfter。logBefore 方法在方法执行前记录开始时间和请求信息,而 logAfter 方法在方法执行后记录结束时间和执行时间。
2. 配置切点表达式:
在切面类中,我们使用 @Before 和 @After 注解分别标注了 logBefore 和 logAfter 方法,并指定了切点表达式 "execution(* com.example.controller.*.*(..))"。这个切点表达式表示我们希望拦截所有 com.example.controller 包下的方法执行。
3. 启用AspectJ支持:
确保在Spring配置文件中启用了AspectJ的支持。可以通过以下配置实现:
<aop:aspectj-autoproxy />
在Spring Boot应用的配置类中,保证@Aspect标记的切面类正确被容器管理即可。
4. 结果:
当用户发起HTTP请求时,切面会自动拦截匹配的方法,记录请求的开始时间和结束时间,并输出到日志中。这样,我们就能够实时监控每个请求的性能,并在需要时进行故障排除。
2. 知识补充
2.1 切点和连接点的概念
在Spring框架中,切点(Pointcut)和连接点(JoinPoint)是实现切面编程的两个核心概念。切点定义了在应用程序中哪些地方切入(或触发)切面的逻辑,而连接点则代表在应用程序执行过程中的具体执行点。连接点可以是方法的调用、方法的执行、异常的抛出等。理解这两个概念是理解切点表达式的基础。
- 连接点(JoinPoint)是程序执行的特定点,它可以是方法的执行、方法的调用、对象的创建等。在Spring AOP中,连接点通常表示方法的执行。连接点是AOP切面可以插入的地方,例如,我们可以在方法调用之前或之后插入额外的逻辑。
- 切点(Pointcut)是一个表达式,它定义了连接点的集合。换句话说,切点确定了在哪些连接点上切入切面逻辑。Spring框架支持多种切点表达式的定义,其中最常用的是AspectJ切点表达式。
2.1.1 AspectJ切点表达式
AspectJ是一种强大的面向切面编程(AOP)语言,Spring框架引入了AspectJ切点表达式以方便开发者定义切点。AspectJ切点表达式使用一种类似于正则表达式的语法来匹配连接点。
AspectJ切点表达式的语法包括以下几个关键部分:
- execution关键字:用于指定要匹配的方法执行连接点。
//匹配com.example.service包中的所有类的所有方法
execution(* com.example.service.*.*(..))
- 访问修饰符和返回类型:可以使用通配符来匹配任意修饰符或返回类型。
//匹配任何公共方法的执行。
execution(public * com.example.service.*.*(..))
- 包和类的限定符:用于指定包和类的名称,通配符`*`可用于匹配任意字符。
//匹配com.example.service包中UserService类的所有方法执行。
execution(* com.example.service.UserService.*(..))
- 方法名:可以指定具体的方法名或使用通配符匹配多个方法。
//匹配UserService类中以"get"开头的所有方法执行。
execution(* com.example.service.UserService.get*(..))
- 参数列表:可以使用“ (..) ”来匹配任意参数列表。
//匹配UserService类的所有方法执行,无论参数列表如何
execution(* com.example.service.UserService.*(..))
AspectJ切点表达式的灵活性使开发者能够定义精确的切点,以满足不同的应用需求。通过深入学习和掌握AspectJ切点表达式,开发者可以更好地利用Spring AOP来管理应用程序中的横切关注点。接下来,我们将深入研究切点表达式的实现原理,以更好地理解Spring框架是如何解析和匹配这些表达式的。
3. 实现原理
3.1代码分支
3.2 核心代码
ClassFilter 和 MethodMatcher 接口
- ClassFilter:该接口用于筛选出应该应用切面的目标类。在Pointcut表达式中,如果没有指定特定的目标类,ClassFilter将返回true,表示匹配任何类。否则,它将根据指定的规则筛选出匹配的类。
- MethodMatcher:这个接口用于匹配目标类中的方法。MethodMatcher决定了哪些方法会成为连接点,从而被切面拦截。MethodMatcher接口包括两个方法:`matches(Method method, Class<?> targetClass)` 用于匹配方法,和 `isRuntime()` 用于表示匹配是否需要在运行时进行动态计算。
AspectJExpressionPointcut 的简单实现
- 表达式解析:首先,AspectJExpressionPointcut会将切点表达式进行解析,将其转化为内部的数据结构,以便进行进一步处理。这个解析过程涉及到词法分析和语法分析,以确保切点表达式的语法正确性。
- 连接点匹配:一旦切点表达式被解析,AspectJExpressionPointcut 将会使用 ClassFilter 和 MethodMatcher 接口来匹配连接点。它会遍历应用程序中的类和方法,根据表达式的定义,确定哪些连接点符合切点表达式的要求。
- 运行时动态匹配:在某些情况下,切点表达式可能需要在运行时动态计算。例如,当表达式中包含参数绑定时,需要在实际方法执行时才能确定是否匹配。AspectJExpressionPointcut会在运行时进行动态匹配,以确保准确的连接点匹配。
下面将借助aspectjweaver的功能简单实现Spring AOP切点表达式功能,实现对execution函数的支持。
3.2.1 首先添加maven坐标
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.0</version>
</dependency>
| 特征 | Spring AOP | AspectJ |
|---|---|---|
| 编程模型 | 基于代理的编程模型,使用 Spring 代理生成 AOP 代理。 | 纯粹基于注解或 XML 的编程模型,使用 AspectJ 编译器或运行时织入器。 |
| 编织方式 | 运行时织入,通过代理包装目标对象来添加切面行为。 | 支持编译时织入和运行时织入,更灵活且功能更强大。 |
| 性能 | 由于使用代理,性能开销较小,但有些限制。 | 性能较好,编译时织入可以最小化运行时开销。 |
| 支持的切入点表达式(Pointcut) | 仅支持一部分切入点表达式,如方法执行(execution)。 | 支持广泛的切入点表达式,包括访问、调用、初始化等多种方式。 |
| 复杂度 | 适用于简单的切面需求,易于配置和使用。 | 适用于复杂的切面需求,提供更多高级功能和灵活性。 |
| 集成度 | 紧密集成到 Spring 框架中,易于使用和配置。 | 相对独立,需要额外配置 AspectJ 编译器或运行时织入器。 |
| 配置方式 | 使用 Spring 的注解或 XML 配置来定义切面。 | 使用 AspectJ 注解或 XML 配置来定义切面。 |
3.2.2 ClassFilter接口
public interface ClassFilter {
boolean matches(Class<?> clazz);
}
3.2.3 MethodMatcher接口
public interface MethodMatcher {
boolean matches(Method method, Class<?> targetClass);
}
3.2.4 Pointcut 切点接口
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
3.2.5 AspectJExpressionPointcut 切点表达式类
/**
* ● @author: YiHui
* ● @date: Created in 17:33 2023/9/24
* ● @Description: 这是一个自定义的 AspectJ 表达式切点,用于在 Spring AOP 中匹配切点表达式。
*/
public class AspectJExpressionPointcut implements Pointcut, ClassFilter, MethodMatcher {
// 支持的切点原语集合
private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();
static {
// 添加支持的切点原语。在此示例中,我们仅支持 EXECUTION 原语,您可以根据需要添加更多。
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
}
// 切点表达式对象,用于解析和匹配切点
private final PointcutExpression pointcutExpression;
/**
* 构造函数,用给定的表达式创建 AspectJ 表达式切点。
*
* @param expression 切点表达式,用于定义匹配的切点
*/
public AspectJExpressionPointcut(String expression) {
// 创建一个 PointcutParser 实例,用于解析切点表达式
PointcutParser pointcutParser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
SUPPORTED_PRIMITIVES, this.getClass().getClassLoader());
// 解析给定的切点表达式并将其分配给成员变量 pointcutExpression
pointcutExpression = pointcutParser.parsePointcutExpression(expression);
}
/**
* 检查给定的类是否符合切点表达式的条件。
*
* @param clazz 要检查的类
* @return 如果类匹配切点表达式,则返回 true,否则返回 false
*/
@Override
public boolean matches(Class<?> clazz) {
return pointcutExpression.couldMatchJoinPointsInType(clazz);
}
/**
* 检查给定的方法是否符合切点表达式的条件。
*
* @param method 要检查的方法
* @param targetClass 方法所属的目标类
* @return 如果方法匹配切点表达式,则返回 true,否则返回 false
*/
@Override
public boolean matches(Method method, Class<?> targetClass) {
// 使用切点表达式检查方法执行是否匹配
return pointcutExpression.matchesMethodExecution(method).alwaysMatches();
}
/**
* 获取用于类筛选的 ClassFilter 实例。
*
* @return ClassFilter 实例,用于过滤匹配的类
*/
@Override
public ClassFilter getClassFilter() {
return this;
}
/**
* 获取用于方法匹配的 MethodMatcher 实例。
*
* @return MethodMatcher 实例,用于匹配符合切点表达式的方法
*/
@Override
public MethodMatcher getMethodMatcher() {
return this;
}
}
4. 测试
4.1测试代码
public class HelloService {
public String hello() {
System.out.println("hello word! yihuiComeOn");
return "hello word! yihuiComeOn";
}
}
public class PointcutExpressionTest {
@Test
public void testPointcutExpression() throws Exception {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut("execution(* service.HelloService.*(..))");
Class<HelloService> clazz = HelloService.class;
Method method = clazz.getDeclaredMethod("hello");
System.out.println("切点表达式匹配结果-类匹配:"+pointcut.matches(clazz));
System.out.println("切点表达式匹配结果-方法匹配:"+pointcut.matches(method, clazz));
}
}
4.2 测试结果
切点表达式匹配结果-类匹配:true
切点表达式匹配结果-方法匹配:true
[源码系列:手写spring] AOP第一节:切点表达式的更多相关文章
- 框架源码系列十:Spring AOP(AOP的核心概念回顾、Spring中AOP的用法、Spring AOP 源码学习)
一.AOP的核心概念回顾 https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#a ...
- Spring 源码学习笔记10——Spring AOP
Spring 源码学习笔记10--Spring AOP 参考书籍<Spring技术内幕>Spring AOP的实现章节 书有点老,但是里面一些概念还是总结比较到位 源码基于Spring-a ...
- 《四 spring源码》手写springioc框架
手写SpringIOCXML版本 /** * 手写Spring专题 XML方式注入bean * * * */ public class ClassPathXmlApplicationContext { ...
- 曹工说Spring Boot源码(18)-- Spring AOP源码分析三部曲,终于快讲完了 (aop:config完整解析【下】)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 手写Spring AOP,快来瞧一瞧看一看撒!
目录 AOP分析 Advice实现 定义Advice接口 定义前置.后置.环绕和异常增强接口 Pointcut实现 定义PointCut接口 定义正则表达式的实现类:RegExpressionPoin ...
- 框架源码系列三:手写Spring AOP(AOP分析、AOP概念学习、切面实现、织入实现)
一.AOP分析 问题1:AOP是什么? Aspect Oriented Programming 面向切面编程,在不改变类的代码的情况下,对类方法进行功能增强. 问题2:我们需要做什么? 在我们的框架中 ...
- Spring源码 20 手写模拟源码
参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...
- 《四 spring源码》手写springmvc
手写SpringMVC思路 1.web.xml加载 为了读取web.xml中的配置,我们用到ServletConfig这个类,它代表当前Servlet在web.xml中的配置信息.通过web.xml ...
- 框架源码系列六:Spring源码学习之Spring IOC源码学习
Spring 源码学习过程: 一.搞明白IOC能做什么,是怎么做的 1. 搞明白IOC能做什么? IOC是用为用户创建.管理实例对象的.用户需要实例对象时只需要向IOC容器获取就行了,不用自己去创建 ...
- 源码分析 | 手写mybait-spring核心功能(干货好文一次学会工厂bean、类代理、bean注册的使用)
作者:小傅哥 博客:https://bugstack.cn - 汇总系列原创专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言介绍 一个知识点的学习过程基本分为:运行helloworld ...
随机推荐
- Java Web学生自习管理系统
一.项目背景与需求分析 随着网络技术的不断发展和学校规模的扩大,学生自习管理系统的需求日益增加.传统的自习管理方式存在效率低下.资源浪费等问题,因此,开发一个智能化的学生自习管理系统显得尤为重要.该系 ...
- Circos软件学习
circos 是一款perl 语言开发的画图软件,提供了染色体相关数据的一种可视化方式.其制作的图表精美,被科研工作者广泛使用.Circos可以对染色体相关数据进行可视化,以每条染色体为一个扇区,组成 ...
- C Primer Plus 第6版 第五章 编程练习参考答案
编译环境VS Code+WSL GCC 源码在文末下载 /*第1题*************************/ #include<stdio.h> #define MIN_TO_H ...
- Windows安全加固(一)
目录: 1.在win ser2016中如何管理重命名administrator,禁用GUEST 2.禁用GUEST账户 3.系统不显示上次登录的账户名. 4.清理系统无效账户. 5.按用户类型分配账号 ...
- 优化永不止步:TinyVue v3.20.0 正式发布,更美观的官网UI,更友好的文档搜索,更强大的主题配置能力~
你好,我是 Kagol,个人公众号:前端开源星球. 我们非常高兴地宣布,2024年12月4日,TinyVue 发布了 v3.20.0 . 本次 3.20.0 版本主要有以下重大变更: OpenTiny ...
- [rustGUI][iced]基于rust的GUI库iced(0.13)的部件学习(03):图像的导入、显示、调整(暨image部件的使用介绍)
前言 本文是关于iced库的部件介绍,iced库是基于rust的GUI库,作者自述是受Elm启发. iced目前的版本是0.13.1,相较于此前的0.12版本,有较大改动. 本合集是基于新版本的关于分 ...
- UWP Shadow 阴影
参考文字: https://mtaulty.com/2016/08/10/windows-10-uwp-and-composition-light-and-shade/ <Grid Backgr ...
- 【FAQ】HarmonyOS SDK 闭源开放能力 —Map Kit(4)
1.问题描述: 添加了很多的marker点,每个marker点都设置了customInfoWindow,但是每次只能显示一个customInfoWindow吗? 解决方案: Marker的InfoWi ...
- AI编程:Coze + Cursor实现一个思维导图的浏览器插件
这是小卷对AI编程工具学习的第3篇文章,今天以实际开发一个思维导图的需求为例,了解AI编程开发的整个过程 1.效果展示 2.AI编程开发流程 虽然AI编程知识简单对话就行,不过咱要逐步深入到项目开发中 ...
- initiator 连接target
客户端 检查是否发现 [root@kvm1 ~]# iscsiadm --mode discovery --type sendtargets --portal 192.168.114.14 1 ...
https://github.com/yihuiaa/little-spring/tree/pointcut-expression