[源码系列:手写spring] AOP第一节:切点表达式

news2025/1/13 3:04:50

        在本专栏之前的文章中已经带大家熟悉了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-expressionicon-default.png?t=N7T8https://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 AOPAspectJ
编程模型基于代理的编程模型,使用 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





 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1037048.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c++程序内存区域划分

目录 内存区域划分 例题&#xff1a; malloc&#xff0c;calloc和realloc的区别 new和delete 申请空间并初始化 申请多个空间&#xff1a; new和delete对于自定义类型的处理&#xff1a; new和delete一定要匹配 new和malloc以及delete和free的区别 抛异常的使用方法&…

ubuntu 开启笔记本摄像头并修复画面颠倒问题

文章目录 基本环境状况&#xff1a; 没找到摄像头检查 opencv检查系统应用 键盘右侧&#xff0c;硬件层面开启摄像头画面镜像问题 基本环境 笔记本&#xff1a; 联想拯救者 系统&#xff1a; ubuntu 22.04 状况&#xff1a; 没找到摄像头 检查 opencv 使用 cv::VideoCaptu…

vue elemnt封装文件上传 +根据后台接口来上传文件

1.创建一个新的子页面,放文件上传 <template> <div><el-uploadaction"https://jsonplaceholder.typicode.com/posts/"list-type"picture-card":on-preview"handlePictureCardPreview":on-remove"handleRemove">&l…

7.网络原理之TCP_IP

文章目录 1.网络基础1.1认识IP地址1.2子网掩码1.3认识MAC地址1.4一跳一跳的网络数据传输1.5总结IP地址和MAC地址1.6网络设备及相关技术1.6.1集线器&#xff1a;转发所有端口1.6.2交换机&#xff1a;MAC地址转换表转发对应端口1.6.3主机&#xff1a;网络分层从上到下封装1.6.4主…

交换机技术综述(第十一课)

交换机技术综述基础 1 Vlan技术的学习 三大接口 Vlan技术实操(第四课)_IHOPEDREAM的博客-CSDN博客https://drean.blog.csdn.net/article/details/132455765?spm=1001.2014.3001.5502

论文阅读:DisCO Portrait Distortion Correction with Perspective-Aware 3D GANs

论文阅读&#xff1a;DisCO Portrait Distortion Correction with Perspective-Aware 3D GANs 今天介绍一篇比较有趣的文章&#xff0c;通过 3D GAN inversion 来解决成像的透视畸变问题 Abstract 文章的摘要&#xff0c;一开始就介绍说&#xff0c;近距离成像的时候&#x…

【3维视觉】20230922_网格编码最新进展

网格编码技术研究进展 1. VDMC编码技术 1.1 VDMC介绍 1.1.1 编码对象 具有时变拓扑的动态网格 1.1.2 技术细节 VDMC的编码和解码过程的高层框图如图2所示[4][5]。预处理模块提供了更好的率失真( Rate-Distortion&#xff0c;RD )性能&#xff0c;支持可伸缩解码和渐进传输…

Minecraft 1.20.x Forge模组开发 06.建筑生成

我们本次尝试在主世界生成一个自定义的建筑。 效果展示 效果展示 效果展示 由于版本更新缘故,1.20的建筑生成将不涉及任何Java包的代码编写,只需要在数据包中对建筑生成进行自定义。 1.首先我们要使用游戏中的结构方块制作一个建筑,结构方块使用教程参考1.16.5自定义建筑生…

记一次hyperf框架封装swoole自定义进程

背景 公司准备引入swoole和rabbitmq来处理公司业务。因此&#xff0c;我引入hyperf框架&#xff0c;想用swoole的多进程来实现。 自定义启动服务封装 <?php /*** 进程启动服务【manager】*/ declare(strict_types1);namespace App\Command;use Swoole; use Swoole\Proce…

软考知识产权基础知识

商标权可以根据需要无限延长 根据《商标法》的规定&#xff0c;商标的有效期为10年&#xff0c;自商标注册之日起计算。有效期届满后&#xff0c;可以递交商标续展申请。每次续展的有效期为10年。但是&#xff0c;商标续展仅限于最后一年有效期也就是期满前六个月内提交申请。…

服务注册发现_actuator微服务信息完善

SpringCloud体系里的&#xff0c;服务实体向eureka注册时&#xff0c;注册名默认是IP名:应用名:应用端口名。 问题&#xff1a; 自定义服务在Eureka上的实例名怎么弄呢 在服务提供者pom中配置Actuator依赖 <!-- actuator监控信息完善 --> <dependency><groupId…

011_第一代软件开发(三)

第一代软件开发(三) 文章目录 第一代软件开发(三)项目介绍带下知识点系统日志滤波器陷波滤波器带通滤波器 打印初始化调用打印机打印文件保存到PDF 总结一下 关键字&#xff1a; Qt、 Qml、 日志、 打印、 滤波器 项目介绍 欢迎来到我们的 QML & C 项目&#xff01;这…

排序算法:非比较排序(计数排序)

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关排序算法的相关知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通…

2018 国际AIOps挑战赛单指标数据集分析

关于数据集 2018年国际AIOps 由中国建设银行、清华大学以及必示科技公司联合举办&#xff0c;尽管已经过去了这么长时间&#xff0c;其提供的比赛数据依然被用于智能运维相关算法的研究。这里我们对此数据集做简单的分析&#xff0c;把一些常用的数据分析方法在这里进行略微地…

Spring面试题13:Spring中ApplicationContext实现有哪些?Bean工厂和Applicationcontext有什么区别

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:Spring中ApplicationContext实现有哪些? 在Spring框架中,有以下几种ApplicationContext的实现: ClassPathXmlApplicationContext:从类路径下的…

基于springboot消防员招录系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

​Segment-and-Track Anything——通用智能视频分割、跟踪、编辑算法解读与源码部署

一、 万物分割 随着Meta发布的Segment Anything Model (万物分割)的论文并开源了相关的算法&#xff0c;我们可以从中看到&#xff0c;SAM与GPT-4类似&#xff0c;这篇论文的目标是&#xff08;零样本&#xff09;分割一切&#xff0c;将自然语言处理&#xff08;NLP&#xff…

【数据结构】二叉排序树;平衡二叉树的知识点学习总结

目录 1、二叉排序树 1.1 定义 1.2 查找操作 1.3 插入操作 1.4 删除操作 1.5 C语言实现二叉排序树的基本操作 2、平衡二叉树的知识点总结 2.1 定义 2.2 插入操作 2.3 调整“不平衡” 2.4 删除操作 1、二叉排序树 1.1 定义 二叉排序树&#xff08;Binary Search …

云计算与大数据——部署Hadoop集群并运行MapReduce集群(超级详细!)

云计算与大数据——部署Hadoop集群并运行MapReduce集群(超级详细&#xff01;) Linux搭建Hadoop集群(CentOS7hadoop3.2.0JDK1.8Mapreduce完全分布式集群) 本文章所用到的版本号&#xff1a; CentOS7 Hadoop3.2.0 JDK1.8 基本概念及重要性 很多小伙伴部署集群用hadoop用mapr…

C++设计模式_06_Decorator 装饰模式

本篇将会介绍Decorator 装饰模式&#xff0c;它是属于一个新的类别&#xff0c;按照C设计模式_03_模板方法Template Method中介绍的划分为“单一职责”模式。 “单一职责”模式讲的是在软件组件的设计中&#xff0c;如果责任划分的不清晰&#xff0c;使用继承得到的结果往往是随…