From 1efafee35e07b78d77b711ad68b498d735050485 Mon Sep 17 00:00:00 2001 From: linlei Date: Wed, 10 Apr 2024 11:39:03 +0800 Subject: [PATCH] =?UTF-8?q?MethodMatcher=E6=BA=90=E7=A0=81=E5=88=86?= =?UTF-8?q?=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- spring-aop/spring-aop-methodMatcher/README.md | 212 ++++++++++++++++++ .../com/xcs/spring/MethodMatcherDemo.java | 30 +++ .../java/com/xcs/spring/MyAnnotation.java | 11 + .../main/java/com/xcs/spring/MyService.java | 8 + 5 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 spring-aop/spring-aop-methodMatcher/README.md create mode 100644 spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MethodMatcherDemo.java create mode 100644 spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MyAnnotation.java create mode 100644 spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MyService.java diff --git a/README.md b/README.md index 8199d17..2a517ea 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,8 @@ + Spring AOP - [AopProxy](spring-aop/spring-aop-aopProxy/README.md):创建和管理AOP代理对象。 - - [ClassFilter](spring-aop/spring-aop-classFilter/README.md):用于指定Spring AOP切面应拦截的目标类。 + - [ClassFilter](spring-aop/spring-aop-classFilter/README.md):确定类是否匹配拦截条件。 + - [MethodMatcher](spring-aop/spring-aop-methodMatcher/README.md):确定方法是否匹配拦截条件。 - [Pointcut](spring-aop/spring-aop-pointcut/README.md):定义切入点,匹配被拦截的方法。 - [Advice](spring-aop/spring-aop-advice/README.md):AOP核心接口,定义切面通知行为。 - [Advisor](spring-aop/spring-aop-advisor/README.md):用于将通知和切点结合,实现切面编程的横切关注点。 diff --git a/spring-aop/spring-aop-methodMatcher/README.md b/spring-aop/spring-aop-methodMatcher/README.md new file mode 100644 index 0000000..eb1aec6 --- /dev/null +++ b/spring-aop/spring-aop-methodMatcher/README.md @@ -0,0 +1,212 @@ +## MethodMatcher + +- [MethodMatcher](#MethodMatcher) + - [一、基本信息](#一基本信息) + - [二、知识储备](#二知识储备) + - [三、基本描述](#三基本描述) + - [四、主要功能](#四主要功能) + - [五、接口源码](#五接口源码) + - [六、主要实现](#六主要实现) + - [七、最佳实践](#七最佳实践) + - [八、与其他组件的关系](#八与其他组件的关系) + - [九、常见问题](#九常见问题) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、知识储备 + +1. **切点表达式语言** + + + 了解切点表达式语言,如Spring AOP中常用的AspectJ切点表达式语言。掌握如何使用切点表达式来描述切点,以及如何使用通配符、逻辑运算符等进行切点匹配。 + +2. **设计模式** + + + 了解设计模式,特别是代理模式和策略模式对于理解Spring AOP中的代理对象和不同类型的MethodMatcher实现很有帮助。 + +### 三、基本描述 + +`MethodMatcher` 接口是Spring AOP中的一个关键接口,用于判断一个给定的方法是否匹配指定的切点表达式。它定义了方法匹配的规则和逻辑,我们可以通过实现这个接口来自定义方法匹配的行为,从而实现针对特定方法的切面逻辑的拦截和执行。 + +### 四、主要功能 + +1. **方法匹配** + + + 判断一个给定的方法是否符合指定的切点表达式,即确定是否应该对该方法进行拦截和应用额外的逻辑。 + +2. **静态匹配** + + + 可以静态地匹配方法,这意味着方法的匹配逻辑可以在编译时确定,并且在整个应用程序的生命周期内保持不变。 + +3. **动态匹配** + + + 有些切点需要在运行时根据方法的参数或其他条件来动态确定匹配与否,`MethodMatcher` 接口也支持这种动态匹配的能力。 + +4. **运行时效率** + + + `MethodMatcher` 的实现应该具有高效率,尤其是在动态匹配的情况下,以避免对应用程序性能造成过大的负担。 + +5. **可扩展性** + + + `MethodMatcher` 接口的设计应该具有良好的扩展性,我们可以根据实际需求自定义方法匹配的规则和逻辑,以满足不同的业务场景和需求。 + +### 五、接口源码 + +`MethodMatcher` 接口用于检查目标方法是否符合通知的条件。它支持静态匹配和动态匹配两种方式,静态匹配在编译时确定,而动态匹配在运行时根据方法参数和先前通知的执行情况进行判断。 + +```java +/** + * {@link Pointcut}的一部分:检查目标方法是否符合通知的条件。 + * + *

MethodMatcher 可以静态动态地评估。 + * 静态匹配涉及方法和(可能的)方法属性。 + * 动态匹配还可以使调用的参数可用,并且可以考虑到之前应用于连接点的先前通知的任何效果。 + * + *

如果实现从其{@link #isRuntime()}方法返回{@code false},则可以静态地执行评估, + * 并且对于此方法的所有调用,无论其参数如何,结果都将相同。 + * 这意味着如果{@link #isRuntime()}方法返回{@code false},则永远不会调用 3-arg + * {@link #matches(java.lang.reflect.Method, Class, Object[])} 方法。 + * + *

如果实现从其 2-arg {@link #matches(java.lang.reflect.Method, Class)} 方法返回{@code true}, + * 并且其{@link #isRuntime()}方法返回{@code true},则将在每次相关通知的潜在执行之前 + * 调用 3-arg {@link #matches(java.lang.reflect.Method, Class, Object[])} 方法, + * 以决定是否应该运行通知。 + * 所有先前的通知,例如拦截器链中的较早拦截器,都将已运行,因此在评估时将可用参数或ThreadLocal状态的任何状态更改。 + * + *

此接口的具体实现通常应提供{@link Object#equals(Object)}和{@link Object#hashCode()}的正确实现, + * 以便允许在缓存方案中使用匹配器 - 例如,由CGLIB生成的代理。 + * + * @author Rod Johnson + * @since 11.11.2003 + * @see Pointcut + * @see ClassFilter + */ +public interface MethodMatcher { + + /** + * 执行静态检查,确定给定的方法是否匹配。 + *

如果此方法返回{@code false},或者{@link #isRuntime()}方法返回{@code false}, + * 则不会进行运行时检查(即不会调用 {@link #matches(java.lang.reflect.Method, Class, Object[])} 方法)。 + * @param method 候选方法 + * @param targetClass 目标类 + * @return 此方法是否静态匹配 + */ + boolean matches(Method method, Class targetClass); + + /** + * 此 MethodMatcher 是否是动态的,也就是说,即使 2-arg matches 方法返回 {@code true}, + * 在运行时是否必须对 {@link #matches(java.lang.reflect.Method, Class, Object[])} 方法进行最终调用? + *

可以在创建AOP代理时调用,不需要在每次方法调用之前再次调用。 + * @return 是否需要运行时匹配 + */ + boolean isRuntime(); + + /** + * 检查此方法是否存在运行时(动态)匹配,此匹配必须已经通过静态匹配。 + *

仅在给定方法和目标类的 2-arg matches 方法返回{@code true}, + * 并且 {@link #isRuntime()} 方法返回{@code true} 时才会调用此方法。 + * 在潜在运行通知之前立即调用,之前的通知链中的所有通知已运行。 + * @param method 候选方法 + * @param targetClass 目标类 + * @param args 方法的参数 + * @return 是否存在运行时匹配 + * @see MethodMatcher#matches(Method, Class) + */ + boolean matches(Method method, Class targetClass, Object... args); + + /** + * 匹配所有方法的规范实例。 + */ + MethodMatcher TRUE = TrueMethodMatcher.INSTANCE; + +} +``` + +### 六、主要实现 + +1. **AnnotationMethodMatcher** + + + 这个类是用于匹配带有特定注解的方法的方法匹配器。它可以用来创建切点,以便对带有特定注解的方法进行拦截和增强。 + +2. **ControlFlowPointcut** + + + 控制流切点用于定义在特定的方法调用链中触发通知的位置。它允许我们指定只有在控制流程满足某些条件时才触发通知。 + +3. **JdkRegexpMethodPointcut** + + + 这个类使用基于正则表达式的方法匹配来创建切点。它允许我们根据方法的名称来定义匹配规则,从而决定哪些方法应该被拦截。 + +4. **NameMatchMethodPointcut** + + + 这个类是基于方法名称的匹配器,它允许我们根据方法的名称模式来定义切点。只要方法名称匹配指定的模式,就可以触发通知。 + +5. **AspectJExpressionPointcut** + + + 这个类使用 AspectJ 表达式语言来创建切点,它允许我们使用更加灵活和强大的语法来定义切点。AspectJ 表达式支持更多的特性,包括访问方法参数、异常类型等。 + +### 七、最佳实践 + +使用不同类型的方法匹配器来检查特定方法是否满足不同的条件。其中,使用 AnnotationMethodMatcher 来检查方法是否具有特定的注解;使用 AspectJExpressionPointcut 基于 AspectJ 表达式来匹配方法;使用 NameMatchMethodPointcut 基于方法名称来匹配方法;使用 JdkRegexpMethodPointcut 基于正则表达式来匹配方法。 + +```java +public class MethodMatcherDemo { + + public static void main(String[] args) throws Exception { + // 使用 AnnotationMethodMatcher 检查是否具有特定注解 + AnnotationMethodMatcher methodMatcher = new AnnotationMethodMatcher(MyAnnotation.class); + System.out.println("方法是否具有特定注解: " + methodMatcher.matches(MyService.class.getDeclaredMethod("myMethod"), MyService.class)); + + // 使用 AspectJExpressionPointcut 基于 AspectJ 表达式匹配方法 + AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); + pointcut.setExpression("execution(* com.xcs.spring.MyService.*(..))"); + System.out.println("方法是否匹配 AspectJ 表达式: " + pointcut.matches(MyService.class.getDeclaredMethod("myMethod"), MyService.class)); + + // 使用 NameMatchMethodPointcut 基于方法名称匹配方法 + NameMatchMethodPointcut pointcut2 = new NameMatchMethodPointcut(); + pointcut2.setMappedName("myMethod"); + System.out.println("方法是否匹配指定名称: " + pointcut2.matches(MyService.class.getDeclaredMethod("myMethod"), MyService.class)); + + // 使用 JdkRegexpMethodPointcut 基于正则表达式匹配方法 + JdkRegexpMethodPointcut pointcut3 = new JdkRegexpMethodPointcut(); + pointcut3.setPattern(".*my.*"); + System.out.println("方法是否匹配正则表达式: " + pointcut3.matches(MyService.class.getDeclaredMethod("myMethod"), MyService.class)); + } +} +``` + +### 八、与其他组件的关系 + +1. **Pointcut** + + + `MethodMatcher` 接口是 `Pointcut` 接口的一部分,用于确定哪些方法应该被拦截和应用额外逻辑的地方。`MethodMatcher` 接口的实现类通常被用来定义切点的匹配条件。 + +2. **Advisor** + + + `MethodMatcher` 接口常常用于创建切面通知器,它决定了切面通知器应该在哪些方法上生效。切面通知器通常会关联一个切点,而切点又会使用 `MethodMatcher` 来确定哪些方法被匹配。 + +3. **Advice** + + + 通知是在切点上执行的附加逻辑。`MethodMatcher` 可以用来限制通知应该应用在哪些方法上。这意味着只有当方法匹配 `MethodMatcher` 的条件时,通知才会被触发。 + +4. **ProxyFactory** + + + 当我们使用 Spring AOP 创建代理时,`MethodMatcher` 通常用于确定哪些方法需要被代理,并且哪些方法应该被排除在代理之外。 + +### 九、常见问题 + +1. **如何编写自定义的 MethodMatcher 实现?** + + + 我们可能想要实现自定义的 `MethodMatcher` 接口以满足特定的匹配需求。在这种情况下,他们需要了解接口的方法以及如何在实现中正确地实现匹配逻辑。 + +2. **如何使用不同类型的 MethodMatcher?** + + + Spring AOP 提供了多种类型的 `MethodMatcher` 实现,如基于名称、基于注解、基于正则表达式、基于 AspectJ 表达式等。了解如何选择并使用正确的类型的 `MethodMatcher` 对于实现所需的匹配逻辑至关重要。 + +3. **如何与 Pointcut 一起使用 MethodMatcher?** + + + `MethodMatcher` 接口通常作为切点(Pointcut)的一部分使用。我们需要了解如何创建和配置切点,并将适当的 `MethodMatcher` 与之关联,以便在 AOP 拦截器中选择正确的方法。 + +4. **如何处理动态匹配需求?** + + + 在某些情况下,方法匹配可能需要在运行时动态确定,而不是静态地在代理创建时确定。我们需要了解如何处理动态匹配需求,并确定是否需要实现 `isRuntime()` 方法以支持此功能。 \ No newline at end of file diff --git a/spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MethodMatcherDemo.java b/spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MethodMatcherDemo.java new file mode 100644 index 0000000..5351563 --- /dev/null +++ b/spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MethodMatcherDemo.java @@ -0,0 +1,30 @@ +package com.xcs.spring; + +import org.springframework.aop.aspectj.AspectJExpressionPointcut; +import org.springframework.aop.support.JdkRegexpMethodPointcut; +import org.springframework.aop.support.NameMatchMethodPointcut; +import org.springframework.aop.support.annotation.AnnotationMethodMatcher; + +public class MethodMatcherDemo { + + public static void main(String[] args) throws Exception { + // 使用 AnnotationMethodMatcher 检查是否具有特定注解 + AnnotationMethodMatcher methodMatcher = new AnnotationMethodMatcher(MyAnnotation.class); + System.out.println("方法是否具有特定注解: " + methodMatcher.matches(MyService.class.getDeclaredMethod("myMethod"), MyService.class)); + + // 使用 AspectJExpressionPointcut 基于 AspectJ 表达式匹配方法 + AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); + pointcut.setExpression("execution(* com.xcs.spring.MyService.*(..))"); + System.out.println("方法是否匹配 AspectJ 表达式: " + pointcut.matches(MyService.class.getDeclaredMethod("myMethod"), MyService.class)); + + // 使用 NameMatchMethodPointcut 基于方法名称匹配方法 + NameMatchMethodPointcut pointcut2 = new NameMatchMethodPointcut(); + pointcut2.setMappedName("myMethod"); + System.out.println("方法是否匹配指定名称: " + pointcut2.matches(MyService.class.getDeclaredMethod("myMethod"), MyService.class)); + + // 使用 JdkRegexpMethodPointcut 基于正则表达式匹配方法 + JdkRegexpMethodPointcut pointcut3 = new JdkRegexpMethodPointcut(); + pointcut3.setPattern(".*my.*"); + System.out.println("方法是否匹配正则表达式: " + pointcut3.matches(MyService.class.getDeclaredMethod("myMethod"), MyService.class)); + } +} diff --git a/spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MyAnnotation.java b/spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MyAnnotation.java new file mode 100644 index 0000000..5dbd4fa --- /dev/null +++ b/spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MyAnnotation.java @@ -0,0 +1,11 @@ +package com.xcs.spring; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface MyAnnotation { +} diff --git a/spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MyService.java b/spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MyService.java new file mode 100644 index 0000000..5e89cad --- /dev/null +++ b/spring-aop/spring-aop-methodMatcher/src/main/java/com/xcs/spring/MyService.java @@ -0,0 +1,8 @@ +package com.xcs.spring; + +public class MyService { + + @MyAnnotation + public void myMethod() { + } +}