[源码系列:手写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 ...
随机推荐
- IDEA配置Maven(详细版)
https://blog.csdn.net/qq_42057154/article/details/106114515 IDEA配置MavenIDEA创建Maven工程第一节 IDEA集成Maven插 ...
- 深入理解ReentrantLock的实现原理
文章目录ReentrantLock简介AQS回顾ReentrantLock原理ReentrantLock结构非公平锁的实现原理lock方法获取锁tryRelease锁的释放公平锁的实现原理lock方法 ...
- 阿里云-数据库-ClickHouse
https://help.aliyun.com/product/144466.html 云数据库ClickHouse是开源列式数据库管理系统ClickHouse在阿里云上的托管服务,用户可以在阿里云上 ...
- Delphi中的变体Variant数组相关函数
转自:Delphi中的变体Variant数组相关函数 1.VarArrayCreate procedure TForm1.Button1Click(Sender: TObject); var V, ...
- NAT原理:概念、使用场景、转发流程及规则
本文分享自天翼云开发者社区<NAT原理:概念.使用场景.转发流程及规则>,作者:x****n 网络地址转换(NAT)是一种在计算机网络中将一个网络的IP地址转换为另一个网络的IP地址的技术 ...
- CPU算力如何计算
本文分享自天翼云开发者社区<CPU算力如何计算>,作者:l****n 什么是算力 随着国家大力发展数字基础设施,算力的提升和普惠变得越来越重要,它注定会在人们的视线中占据很重要的一席.那么 ...
- ceph数据重构原理
本文分享自天翼云开发者社区<ceph数据重构原理>,作者:x****n 在分布式存储系统Ceph中,硬盘故障是一种常见问题.为了保证数据安全,当发生硬盘故障后,分布式存储系统会依据算法对故 ...
- Flink流式数据缓冲后批量写入Clickhouse
一.背景 对于clickhouse有过使用经验的开发者应该知道,ck的写入,最优应该是批量的写入.但是对于流式场景来说,每批写入的数据量都是不可控制的,如kafka,每批拉取的消息数量是不定的,fli ...
- HT-014 Div3 扫雷 题解 [ 绿 ] [ 二维差分 ]
分析 观察到是曼哈顿距离 \(\le r\) 的范围可以扫到,联想到如下图形: 左边是 \(r=1\) 可以扫到的范围,右边是 \(r=2\) 可以扫到的范围. 于是,我们只要对这样的图形在 \(10 ...
- Git钩子-每次提交信息添加分支名称
Git钩子是一组脚本,这些脚本对应着Git仓库中的特定事件,每一次事件发生时,钩子会被触发.这允许你可以定制化Git的内部行为,在开发周期中的关键点上触发执行定制化的脚本. 钩子脚本文件通常放置于项目 ...
https://github.com/yihuiaa/little-spring/tree/pointcut-expression