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


            在本专栏之前的文章中已经带大家熟悉了Spirng中核心概念IOC的原理以及手写了核心代码,接下来将继续介绍Spring中另一核心概念AOP。
            AOP即切面编程是Spring框架中的一个关键概念,它允许开发者在应用程序中优雅地处理横切关注点,如日志记录、性能监控和事务管理。在切面编程中,切点表达式是一项关键技术,它定义了在何处应用切面的逻辑。本章将深入探讨Spring切点表达式的实现原理,为读者提供对这一重要概念的深刻理解。

    1.AOP案例

    1.1 案例背景

            假设我们有一个在线商城的Web应用,用户可以浏览商品、下单购买商品等。我们希望记录每个HTTP请求的开始时间、结束时间以及执行时间,以便监控应用的性能并快速定位潜在的问题。

    1.2 AOP解决方案

    我们可以使用Spring AOP和切点表达式来实现这个日志记录功能。以下是实现步骤:


    1. 创建一个切面类:

    1. import org.aspectj.lang.JoinPoint;
    2. import org.aspectj.lang.annotation.After;
    3. import org.aspectj.lang.annotation.Aspect;
    4. import org.aspectj.lang.annotation.Before;
    5. import org.springframework.stereotype.Component;
    6. @Aspect
    7. @Component
    8. public class RequestLoggingAspect {
    9. private long startTime;
    10. @Before("execution(* com.example.controller.*.*(..))")
    11. public void logBefore(JoinPoint joinPoint) {
    12. startTime = System.currentTimeMillis();
    13. System.out.println("Request received for: " + joinPoint.getSignature().toShortString());
    14. }
    15. @After("execution(* com.example.controller.*.*(..))")
    16. public void logAfter(JoinPoint joinPoint) {
    17. long endTime = System.currentTimeMillis();
    18. long executionTime = endTime - startTime;
    19. System.out.println("Request completed for: " + joinPoint.getSignature().toShortString());
    20. System.out.println("Execution Time: " + executionTime + "ms");
    21. }
    22. }

    上述代码定义了一个切面类 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关键字:用于指定要匹配的方法执行连接点。
    1. //匹配com.example.service包中的所有类的所有方法
    2. execution(* com.example.service.*.*(..))
    • 访问修饰符和返回类型:可以使用通配符来匹配任意修饰符或返回类型。
    1. //匹配任何公共方法的执行。
    2. execution(public * com.example.service.*.*(..))
    • 包和类的限定符:用于指定包和类的名称,通配符`*`可用于匹配任意字符。
    1. //匹配com.example.service包中UserService类的所有方法执行。
    2. execution(* com.example.service.UserService.*(..))
    • 方法名:可以指定具体的方法名或使用通配符匹配多个方法。
    1. //匹配UserService类中以"get"开头的所有方法执行。
    2. execution(* com.example.service.UserService.get*(..))
    • 参数列表:可以使用“ (..) ”来匹配任意参数列表。
    1. //匹配UserService类的所有方法执行,无论参数列表如何
    2. 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坐标

    1. <dependency>
    2. <groupId>org.aspectjgroupId>
    3. <artifactId>aspectjweaverartifactId>
    4. <version>1.8.0version>
    5. dependency>
    特征Spring AOPAspectJ
    编程模型基于代理的编程模型,使用 Spring 代理生成 AOP 代理。纯粹基于注解或 XML 的编程模型,使用 AspectJ 编译器或运行时织入器。
    编织方式运行时织入,通过代理包装目标对象来添加切面行为。支持编译时织入和运行时织入,更灵活且功能更强大。
    性能由于使用代理,性能开销较小,但有些限制。性能较好,编译时织入可以最小化运行时开销。
    支持的切入点表达式(Pointcut)仅支持一部分切入点表达式,如方法执行(execution)。支持广泛的切入点表达式,包括访问、调用、初始化等多种方式。
    复杂度适用于简单的切面需求,易于配置和使用。适用于复杂的切面需求,提供更多高级功能和灵活性。
    集成度紧密集成到 Spring 框架中,易于使用和配置。相对独立,需要额外配置 AspectJ 编译器或运行时织入器。
    配置方式使用 Spring 的注解或 XML 配置来定义切面。使用 AspectJ 注解或 XML 配置来定义切面。

    3.2.2 ClassFilter接口

    1. public interface ClassFilter {
    2. boolean matches(Class clazz);
    3. }

    3.2.3 MethodMatcher接口

    1. public interface MethodMatcher {
    2. boolean matches(Method method, Class targetClass);
    3. }

    3.2.4 Pointcut 切点接口

    1. public interface Pointcut {
    2. ClassFilter getClassFilter();
    3. MethodMatcher getMethodMatcher();
    4. }

    3.2.5 AspectJExpressionPointcut 切点表达式类

    1. /**
    2. * ● @author: YiHui
    3. * ● @date: Created in 17:33 2023/9/24
    4. * ● @Description: 这是一个自定义的 AspectJ 表达式切点,用于在 Spring AOP 中匹配切点表达式。
    5. */
    6. public class AspectJExpressionPointcut implements Pointcut, ClassFilter, MethodMatcher {
    7. // 支持的切点原语集合
    8. private static final Set SUPPORTED_PRIMITIVES = new HashSet<>();
    9. static {
    10. // 添加支持的切点原语。在此示例中,我们仅支持 EXECUTION 原语,您可以根据需要添加更多。
    11. SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
    12. }
    13. // 切点表达式对象,用于解析和匹配切点
    14. private final PointcutExpression pointcutExpression;
    15. /**
    16. * 构造函数,用给定的表达式创建 AspectJ 表达式切点。
    17. *
    18. * @param expression 切点表达式,用于定义匹配的切点
    19. */
    20. public AspectJExpressionPointcut(String expression) {
    21. // 创建一个 PointcutParser 实例,用于解析切点表达式
    22. PointcutParser pointcutParser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
    23. SUPPORTED_PRIMITIVES, this.getClass().getClassLoader());
    24. // 解析给定的切点表达式并将其分配给成员变量 pointcutExpression
    25. pointcutExpression = pointcutParser.parsePointcutExpression(expression);
    26. }
    27. /**
    28. * 检查给定的类是否符合切点表达式的条件。
    29. *
    30. * @param clazz 要检查的类
    31. * @return 如果类匹配切点表达式,则返回 true,否则返回 false
    32. */
    33. @Override
    34. public boolean matches(Class clazz) {
    35. return pointcutExpression.couldMatchJoinPointsInType(clazz);
    36. }
    37. /**
    38. * 检查给定的方法是否符合切点表达式的条件。
    39. *
    40. * @param method 要检查的方法
    41. * @param targetClass 方法所属的目标类
    42. * @return 如果方法匹配切点表达式,则返回 true,否则返回 false
    43. */
    44. @Override
    45. public boolean matches(Method method, Class targetClass) {
    46. // 使用切点表达式检查方法执行是否匹配
    47. return pointcutExpression.matchesMethodExecution(method).alwaysMatches();
    48. }
    49. /**
    50. * 获取用于类筛选的 ClassFilter 实例。
    51. *
    52. * @return ClassFilter 实例,用于过滤匹配的类
    53. */
    54. @Override
    55. public ClassFilter getClassFilter() {
    56. return this;
    57. }
    58. /**
    59. * 获取用于方法匹配的 MethodMatcher 实例。
    60. *
    61. * @return MethodMatcher 实例,用于匹配符合切点表达式的方法
    62. */
    63. @Override
    64. public MethodMatcher getMethodMatcher() {
    65. return this;
    66. }
    67. }

    4. 测试

    4.1测试代码

    1. public class HelloService {
    2. public String hello() {
    3. System.out.println("hello word! yihuiComeOn");
    4. return "hello word! yihuiComeOn";
    5. }
    6. }
    1. public class PointcutExpressionTest {
    2. @Test
    3. public void testPointcutExpression() throws Exception {
    4. AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut("execution(* service.HelloService.*(..))");
    5. Class clazz = HelloService.class;
    6. Method method = clazz.getDeclaredMethod("hello");
    7. System.out.println("切点表达式匹配结果-类匹配:"+pointcut.matches(clazz));
    8. System.out.println("切点表达式匹配结果-方法匹配:"+pointcut.matches(method, clazz));
    9. }
    10. }

    4.2 测试结果

    1. 切点表达式匹配结果-类匹配:true
    2. 切点表达式匹配结果-方法匹配:true





     

  • 相关阅读:
    网络时代下的声音之路:如何在中央新闻媒体发布网评稿
    WuThreat身份安全云-TVD每日漏洞情报-2023-09-21
    Elasticsearch:崭新的打分机制 - Learning To Rank (LTR)
    阳离子脂质DMG-PEG2000;1,2-二肉豆蔻酰-rac-甘油-3-甲氧基聚乙二醇2000
    vue制作拍照、录像功能
    基于WEB的网上购物系统的设计与实现
    【JAVA知识梳理】异常机制
    大数据技术之Hadoop:使用命令操作HDFS(四)
    CISSP学习笔记:人员安全和风险管理概念
    【JavaSE】集合专项练习篇(附源码)
  • 原文地址:https://blog.csdn.net/weixin_43848166/article/details/133214972