在本专栏之前的文章中已经带大家熟悉了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,其中包含两个通知方法 logBeforelogAfterlogBefore 方法在方法执行前记录开始时间和请求信息,而 logAfter 方法在方法执行后记录结束时间和执行时间。

2. 配置切点表达式:

在切面类中,我们使用 @Before@After 注解分别标注了 logBeforelogAfter 方法,并指定了切点表达式 "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代码分支

https://github.com/yihuiaa/little-spring/tree/pointcut-expressionhttps://github.com/yihuiaa/little-spring/tree/pointcut-expression

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第一节:切点表达式的更多相关文章

  1. 框架源码系列十:Spring AOP(AOP的核心概念回顾、Spring中AOP的用法、Spring AOP 源码学习)

    一.AOP的核心概念回顾 https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#a ...

  2. Spring 源码学习笔记10——Spring AOP

    Spring 源码学习笔记10--Spring AOP 参考书籍<Spring技术内幕>Spring AOP的实现章节 书有点老,但是里面一些概念还是总结比较到位 源码基于Spring-a ...

  3. 《四 spring源码》手写springioc框架

    手写SpringIOCXML版本 /** * 手写Spring专题 XML方式注入bean * * * */ public class ClassPathXmlApplicationContext { ...

  4. 曹工说Spring Boot源码(18)-- Spring AOP源码分析三部曲,终于快讲完了 (aop:config完整解析【下】)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  5. 手写Spring AOP,快来瞧一瞧看一看撒!

    目录 AOP分析 Advice实现 定义Advice接口 定义前置.后置.环绕和异常增强接口 Pointcut实现 定义PointCut接口 定义正则表达式的实现类:RegExpressionPoin ...

  6. 框架源码系列三:手写Spring AOP(AOP分析、AOP概念学习、切面实现、织入实现)

    一.AOP分析 问题1:AOP是什么? Aspect Oriented Programming 面向切面编程,在不改变类的代码的情况下,对类方法进行功能增强. 问题2:我们需要做什么? 在我们的框架中 ...

  7. Spring源码 20 手写模拟源码

    参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...

  8. 《四 spring源码》手写springmvc

    手写SpringMVC思路 1.web.xml加载  为了读取web.xml中的配置,我们用到ServletConfig这个类,它代表当前Servlet在web.xml中的配置信息.通过web.xml ...

  9. 框架源码系列六:Spring源码学习之Spring IOC源码学习

    Spring 源码学习过程: 一.搞明白IOC能做什么,是怎么做的  1. 搞明白IOC能做什么? IOC是用为用户创建.管理实例对象的.用户需要实例对象时只需要向IOC容器获取就行了,不用自己去创建 ...

  10. 源码分析 | 手写mybait-spring核心功能(干货好文一次学会工厂bean、类代理、bean注册的使用)

    作者:小傅哥 博客:https://bugstack.cn - 汇总系列原创专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言介绍 一个知识点的学习过程基本分为:运行helloworld ...

随机推荐

  1. IDEA配置Maven(详细版)

    https://blog.csdn.net/qq_42057154/article/details/106114515 IDEA配置MavenIDEA创建Maven工程第一节 IDEA集成Maven插 ...

  2. 深入理解ReentrantLock的实现原理

    文章目录ReentrantLock简介AQS回顾ReentrantLock原理ReentrantLock结构非公平锁的实现原理lock方法获取锁tryRelease锁的释放公平锁的实现原理lock方法 ...

  3. 阿里云-数据库-ClickHouse

    https://help.aliyun.com/product/144466.html 云数据库ClickHouse是开源列式数据库管理系统ClickHouse在阿里云上的托管服务,用户可以在阿里云上 ...

  4. Delphi中的变体Variant数组相关函数

    转自:Delphi中的变体Variant数组相关函数 1.VarArrayCreate procedure TForm1.Button1Click(Sender: TObject); var   V, ...

  5. NAT原理:概念、使用场景、转发流程及规则

    本文分享自天翼云开发者社区<NAT原理:概念.使用场景.转发流程及规则>,作者:x****n 网络地址转换(NAT)是一种在计算机网络中将一个网络的IP地址转换为另一个网络的IP地址的技术 ...

  6. CPU算力如何计算

    本文分享自天翼云开发者社区<CPU算力如何计算>,作者:l****n 什么是算力 随着国家大力发展数字基础设施,算力的提升和普惠变得越来越重要,它注定会在人们的视线中占据很重要的一席.那么 ...

  7. ceph数据重构原理

    本文分享自天翼云开发者社区<ceph数据重构原理>,作者:x****n 在分布式存储系统Ceph中,硬盘故障是一种常见问题.为了保证数据安全,当发生硬盘故障后,分布式存储系统会依据算法对故 ...

  8. Flink流式数据缓冲后批量写入Clickhouse

    一.背景 对于clickhouse有过使用经验的开发者应该知道,ck的写入,最优应该是批量的写入.但是对于流式场景来说,每批写入的数据量都是不可控制的,如kafka,每批拉取的消息数量是不定的,fli ...

  9. HT-014 Div3 扫雷 题解 [ 绿 ] [ 二维差分 ]

    分析 观察到是曼哈顿距离 \(\le r\) 的范围可以扫到,联想到如下图形: 左边是 \(r=1\) 可以扫到的范围,右边是 \(r=2\) 可以扫到的范围. 于是,我们只要对这样的图形在 \(10 ...

  10. Git钩子-每次提交信息添加分支名称

    Git钩子是一组脚本,这些脚本对应着Git仓库中的特定事件,每一次事件发生时,钩子会被触发.这允许你可以定制化Git的内部行为,在开发周期中的关键点上触发执行定制化的脚本. 钩子脚本文件通常放置于项目 ...