Pointcut优化

master
linlei 2024-04-26 14:51:43 +08:00
parent 26e097436b
commit 1b08aae6aa
7 changed files with 206 additions and 174 deletions

View File

@ -1,6 +1,8 @@
package com.xcs.spring;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
public class MethodInterceptorDemo {
@ -8,7 +10,7 @@ public class MethodInterceptorDemo {
// 创建代理工厂&创建目标对象
ProxyFactory proxyFactory = new ProxyFactory(new MyService());
// 创建通知
proxyFactory.addAdvice(new MyMethodInterceptor());
proxyFactory.addAdvisor(new DefaultPointcutAdvisor(Pointcut.TRUE, new MyMethodInterceptor()));
// 获取代理对象
MyService proxy = (MyService) proxyFactory.getProxy();
// 调用代理对象的方法

View File

@ -8,14 +8,14 @@
- [五、主要实现](#五主要实现)
- [六、类关系图](#六类关系图)
- [七、最佳实践](#七最佳实践)
- [自定义Pointcut](#自定义pointcut)
- [MyCustomPointcut](#mycustompointcut)
- [AspectJExpressionPointcut](#aspectjexpressionpointcut)
- [AnnotationMatchingPointcut](#annotationmatchingpointcut)
- [NameMatchMethodPointcut](#namematchmethodpointcut)
- [JdkRegexpMethodPointcut](#jdkregexpmethodpointcut)
- [八、常见问题](#八常见问题)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading)
@ -28,16 +28,15 @@
1. **定义切入点**
+ Pointcut 接口用于定义切入点,即确定哪些方法应该被切面所影响。它允许开发人员指定在哪些类的哪些方法上应用切面。
+ Pointcut 接口用于定义切入点,即确定哪些方法应该被切面所影响。它允许我们指定在哪些类的哪些方法上应用切面。
2. **匹配规则**
+ 提供了匹配规则,以确定在哪些方法上应用切面。这些规则可以基于方法的名称、参数、返回类型、类名称等多种条件来定义,从而实现对切入点的精确定位。
3. **获取类过滤器**
+ `getClassFilter()` 方法用于获取一个 `ClassFilter` 对象,该对象用于确定哪些类应该被匹配。我们可以根据自己的需求自定义类过滤逻辑。
4. **获取方法匹配器**
+ `getMethodMatcher()` 方法用于获取一个 `MethodMatcher` 对象,该对象用于确定哪些方法应该被匹配。我们可以根据自己的需求自定义方法匹配逻辑。
@ -90,9 +89,8 @@ public interface Pointcut {
+ 根据方法名称匹配的切入点。可以配置指定的方法名称或通配符,以匹配目标类中的方法。
2. **JdkRegexpMethodPointcut**
+ 使用正则表达式匹配方法的切入点。可以使用正则表达式指定方法的匹配规则。
3. **AspectJExpressionPointcut**
+ 使用 AspectJ 切入点表达式匹配方法的切入点。可以使用 AspectJ 的语法来定义更灵活的切入点匹配规则。
@ -150,9 +148,9 @@ TruePointcut ..> Pointcut
### 七、最佳实践
#### 自定义Pointcut
#### MyCustomPointcut
使用 Spring AOP 创建代理对象,并应用自定义的切入点和通知来拦截目标方法的调用。首先,通过 `ProxyFactory` 创建了一个代理工厂,然后使用 `addAdvisor` 方法添加了一个切面,其中包含了自定义的切入点和通知。接着,通过代理工厂的 `getProxy` 方法获取代理对象。最后,使用代理对象调用方法
使用自定义的 `Pointcut` 对象 `MyCustomPointcut`。在 `customPointcut` 方法中,我们创建了 `MyCustomPointcut` 的实例,并通过 `showMatchesLog` 方法展示了其对类和方法的匹配情况。最后,我们通过调用 `showMatchesLog` 方法来检查 `MyCustomPointcut` 对象对目标类 `MyService` 中的方法的匹配情况,并输出匹配结果
```java
public class PointcutDemo {
@ -161,20 +159,30 @@ public class PointcutDemo {
}
/**
* 自定义 Pointcut 最佳实践
* 自定义 Pointcut
*/
private static void customPointcut() {
// 创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory(new MyBean());
// 添加切面:使用自定义的切入点和通知构建默认切面
proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new MyCustomPointcut(), new MyCustomAdvice()));
// 获取代理对象
MyBean myBean = (MyBean) proxyFactory.getProxy();
MyCustomPointcut pointcut = new MyCustomPointcut();
showMatchesLog(pointcut);
}
public static void showMatchesLog(Pointcut pointcut) {
try {
Class<MyService> target = MyService.class;
Method getNameMethod = target.getDeclaredMethod("getName");
Method getAgeMethod = target.getDeclaredMethod("getAge");
Method setNameMethod = target.getDeclaredMethod("setName");
// 使用代理对象调用方法
myBean.getName(); // 将被通知拦截
myBean.getAge(); // 将被通知拦截
myBean.setName(); // 不会被通知拦截
ClassFilter classFilter = pointcut.getClassFilter();
MethodMatcher methodMatcher = pointcut.getMethodMatcher();
System.out.println("ClassFilter MyService = " + classFilter.matches(target));
System.out.println("MethodMatcher MyService getName = " + methodMatcher.matches(getNameMethod, target));
System.out.println("MethodMatcher MyService getAge = " + methodMatcher.matches(getAgeMethod, target));
System.out.println("MethodMatcher MyService setName = " + methodMatcher.matches(setNameMethod, target));
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
@ -215,38 +223,39 @@ class MyCustomPointcut implements Pointcut {
}
```
自定义的通知 `MyCustomAdvice`,它实现了 `MethodBeforeAdvice` 接口,因此是一个前置通知,用于在目标方法执行之前执行额外的逻辑。在 `before` 方法中,它输出一条日志信息 "Before advice is executed",表示在目标方法执行前执行了该通知逻辑
`MyService` 类是一个示例服务类,标注了类级别的 `@MyClassAnnotation` 注解,其中包含了三个方法:`getName()`、`setName()` 和 `getAge()`。其中,`setName()` 方法标注了方法级别的 `@MyMethodAnnotation` 注解
```java
class MyCustomAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("Before advice is executed");
@MyClassAnnotation
public class MyService {
public void getName() {
System.out.println("getName...");
}
@MyMethodAnnotation
public void setName() {
System.out.println("setName...");
}
public void getAge() {
System.out.println("getAge...");
}
}
```
定义了一个简单的 Java 类 `MyBean`,其中包含了三个方法`getName()`、`setName()` 和 `getAge()`
运行结果,`MyService` 类级别的过滤器匹配成功,而在方法级别,`getName` 和 `getAge` 方法成功匹配,但 `setName` 方法未匹配成功
```java
public class MyBean {
public void getName() {
System.out.println("getName() method");
}
public void setName() {
System.out.println("setName() method");
}
public void getAge() {
System.out.println("getAge() method");
}
}
ClassFilter MyService = true
MethodMatcher MyService getName = true
MethodMatcher MyService getAge = true
MethodMatcher MyService setName = false
```
#### AspectJExpressionPointcut
使用 `AspectJExpressionPointcut` 实现一个简单的切入点定义。在 `aspectJExpressionPointcut` 方法中,我们创建了一个 `AspectJExpressionPointcut` 对象,并设置了 AspectJ 表达式,该表达式匹配了所有类中的 `getName()` 方法。然后,我们将切入点与通知关联,并将其作为切面添加到代理工厂中。最后,我们使用代理对象调用了几个方法,根据切入点的定义,只有匹配到的方法会被通知拦截
使用 `AspectJExpressionPointcut` 创建一个基于 AspectJ 表达式的切入点。在 `aspectJExpressionPointcut` 方法中,我们创建了 `AspectJExpressionPointcut` 的实例,并设置了 AspectJ 表达式 `"execution(* com.xcs.spring.MyService.get*())"`,该表达式匹配了 `com.xcs.spring.MyService` 类中以 `get` 开头的所有方法。最后,我们通过调用 `showMatchesLog` 方法来检查 `AspectJExpressionPointcut` 对象对指定类中的方法的匹配情况,并输出匹配结果。
```java
public class PointcutDemo {
@ -254,32 +263,30 @@ public class PointcutDemo {
aspectJExpressionPointcut();
}
/**
* AspectJExpressionPointcut最佳实践
/**
* AspectJExpressionPointcut
*/
private static void aspectJExpressionPointcut() {
// 创建 AspectJ 表达式切入点
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* *.getName())");
// 创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory(new MyBean());
// 添加切面:使用自定义的切入点和通知构建默认切面
proxyFactory.addAdvisor(new DefaultPointcutAdvisor(pointcut, new MyCustomAdvice()));
// 获取代理对象
MyBean myBean = (MyBean) proxyFactory.getProxy();
// 使用代理对象调用方法
myBean.getName(); // 将被通知拦截
myBean.getAge(); // 不会被通知拦截
myBean.setName(); // 不会被通知拦截
pointcut.setExpression("execution(* com.xcs.spring.MyService.get*())");
showMatchesLog(pointcut);
}
}
```
运行结果,`MyService` 类级别的过滤器匹配成功,而在方法级别,`getName` 和 `getAge` 方法成功匹配,但 `setName` 方法未匹配成功。
```java
ClassFilter MyService = true
MethodMatcher MyService getName = true
MethodMatcher MyService getAge = true
MethodMatcher MyService setName = false
```
#### AnnotationMatchingPointcut
使用 `AnnotationMatchingPointcut` 实现一个简单的切入点定义。在 `annotationMatchingPointcut` 方法中,我们创建了一个 `AnnotationMatchingPointcut` 对象,并指定了要匹配的注解类型 `MyAnnotation`,以及是否检查继承的方法。然后,我们将切入点与通知关联,并将其作为切面添加到代理工厂中。最后,我们使用代理对象调用了几个方法,根据切入点的定义,所有使用了 `MyAnnotation` 注解的方法都会被通知拦截。
使用 `AnnotationMatchingPointcut` 创建一个基于注解匹配的切入点。在 `annotationMatchingPointcut` 方法中,我们创建了 `AnnotationMatchingPointcut` 的实例,并指定了类级别注解 `MyClassAnnotation` 和方法级别注解 `MyMethodAnnotation`,同时设置了不检查继承的方法。最后,我们通过调用 `showMatchesLog` 方法来检查 `AnnotationMatchingPointcut` 对象对指定类中的方法的匹配情况,并输出匹配结果
```java
public class PointcutDemo {
@ -288,27 +295,28 @@ public class PointcutDemo {
}
/**
* AnnotationMatchingPointcut 最佳实践
* AnnotationMatchingPointcut
*/
private static void annotationMatchingPointcut() {
// 创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory(new MyBean());
// 添加切面使用AnnotationMatchingPointcut切入点和通知构建默认切面
proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new AnnotationMatchingPointcut(MyAnnotation.class, false), new MyCustomAdvice()));
// 获取代理对象
MyBean myBean = (MyBean) proxyFactory.getProxy();
// 使用代理对象调用方法
myBean.getName(); // 将被通知拦截
myBean.getAge(); // 将被通知拦截
myBean.setName(); // 将被通知拦截
// 使用AnnotationMatchingPointcut切入点
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(MyClassAnnotation.class, MyMethodAnnotation.class, false);
showMatchesLog(pointcut);
}
}
```
运行结果,`MyService` 类级别的过滤器匹配成功,而方法级别的匹配器成功匹配了 `setName` 方法,但未匹配 `getName``getAge` 方法。
```java
ClassFilter MyService = true
MethodMatcher MyService getName = false
MethodMatcher MyService getAge = false
MethodMatcher MyService setName = true
```
#### NameMatchMethodPointcut
使用 `NameMatchMethodPointcut` 实现一个简单的切入点定义。在 `nameMatchMethodPointcut` 方法中,我们创建了一个 `NameMatchMethodPointcut` 对象,并添加了要匹配的方法名 `getAge`。然后,我们将切入点与通知关联,并将其作为切面添加到代理工厂中。最后,我们使用代理对象调用了几个方法,根据切入点的定义,只有匹配到的方法会被通知拦截。
使用 `NameMatchMethodPointcut` 创建一个基于方法名匹配的切入点。在 `nameMatchMethodPointcut` 方法中,我们创建了 `NameMatchMethodPointcut` 的实例,并添加了要匹配的方法名 `getAge`。然后,我们通过调用 `showMatchesLog` 方法来检查 `NameMatchMethodPointcut` 对象对指定类中的方法的匹配情况,并输出匹配结果
```java
public class PointcutDemo {
@ -317,54 +325,63 @@ public class PointcutDemo {
}
/**
* AspectJExpressionPointcut最佳实践
* AspectJExpressionPointcut
*/
private static void nameMatchMethodPointcut() {
// 创建方法名匹配切入点
// 使用AnnotationMatchingPointcut切入点
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.addMethodName("getAge");
// 创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory(new MyBean());
// 添加切面:使用自定义的切入点和通知构建默认切面
proxyFactory.addAdvisor(new DefaultPointcutAdvisor(pointcut, new MyCustomAdvice()));
// 获取代理对象
MyBean myBean = (MyBean) proxyFactory.getProxy();
// 使用代理对象调用方法
myBean.getName(); // 不会被通知拦截
myBean.getAge(); // 将被通知拦截
myBean.setName(); // 不会被通知拦截
showMatchesLog(pointcut);
}
}
```
运行结果, `MyService` 类级别的过滤器匹配成功,而方法级别的匹配器成功匹配了 `getAge` 方法,但未匹配 `getName``setName` 方法。
```java
ClassFilter MyService = true
MethodMatcher MyService getName = false
MethodMatcher MyService getAge = true
MethodMatcher MyService setName = false
```
#### JdkRegexpMethodPointcut
使用 `JdkRegexpMethodPointcut` 创建一个基于 JDK 正则表达式匹配的切入点。在 `jdkRegexpMethodPointcut` 方法中,我们创建了 `JdkRegexpMethodPointcut` 的实例,并设置了正则表达式模式 `".*set.*"`,该模式匹配了所有包含 "set" 字符串的方法名。然后,我们通过调用 `showMatchesLog` 方法来检查 `JdkRegexpMethodPointcut` 对象对指定类中的方法的匹配情况,并输出匹配结果。
```java
public class PointcutDemo {
public static void main(String[] args) {
jdkRegexpMethodPointcut();
}
/**
* JdkRegexpMethodPointcut
*/
private static void jdkRegexpMethodPointcut() {
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern(".*set.*");
showMatchesLog(pointcut);
}
}
```
运行结果,`MyService` 类级别的过滤器匹配成功,而方法级别的匹配器成功匹配了 `setName` 方法,但未匹配 `getName``getAge` 方法。
```java
ClassFilter MyService = true
MethodMatcher MyService getName = false
MethodMatcher MyService getAge = false
MethodMatcher MyService setName = true
```
### 八、常见问题
1. **切入点表达式定义错误**
+ 使用 AspectJ 表达式时,可能会由于表达式定义错误导致切入点匹配失败。例如,表达式写错了、漏掉了必要的切入点信息等。
2. **匹配不到目标方法**
+ 定义的切入点可能无法匹配到目标方法,导致通知无法正确地应用。这可能是由于切入点的匹配规则不正确,或者目标方法的特征与切入点不匹配等原因造成的。
3. **切入点过于宽泛**
+ 切入点定义过于宽泛,导致匹配到了不必要的方法,使得通知影响范围过大。这可能会导致性能问题或意外的行为。
+ 切入点定义过于宽泛,导致匹配到了不必要的方法,使得通知影响范围过大。这可能会导致性能问题或意外的行为。
4. **切入点过于狭窄**
+ 切入点定义过于狭窄,导致无法匹配到预期的目标方法,使得通知无法正确应用。这可能会导致切面无法达到预期的效果。
5. **运行时动态匹配问题**
+ 如果使用了运行时动态匹配的切入点,可能会由于动态条件的设置不正确或者动态条件的结果不符合预期等原因导致匹配失败。
6. **与其他切面冲突**
+ 如果多个切面定义了相互冲突的切入点,可能会导致切面的顺序问题或者切面之间的冲突,使得通知的执行顺序出现问题或者切面功能失效。
7. **性能问题**
+ 如果切入点定义过于宽泛或者运行时动态匹配过于频繁,可能会导致性能问题,影响应用程序的性能表现。
+ 切入点定义过于狭窄,导致无法匹配到预期的目标方法,使得通知无法正确应用。这可能会导致切面无法达到预期的效果。

View File

@ -1,16 +0,0 @@
package com.xcs.spring;
@MyAnnotation
public class MyBean {
public void getName() {
System.out.println("getName() method");
}
public void setName() {
System.out.println("setName() method");
}
public void getAge() {
System.out.println("getAge() method");
}
}

View File

@ -10,5 +10,5 @@ import java.lang.annotation.Target;
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
public @interface MyClassAnnotation {
}

View File

@ -0,0 +1,14 @@
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 MyMethodAnnotation {
}

View File

@ -0,0 +1,18 @@
package com.xcs.spring;
@MyClassAnnotation
public class MyService {
public void getName() {
System.out.println("getName...");
}
@MyMethodAnnotation
public void setName() {
System.out.println("setName...");
}
public void getAge() {
System.out.println("getAge...");
}
}

View File

@ -1,11 +1,15 @@
package com.xcs.spring;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import java.lang.reflect.Method;
/**
* @author xcs
* @date 20244715:42:49
@ -13,81 +17,74 @@ import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
public class PointcutDemo {
public static void main(String[] args) {
customPointcut();
// aspectJExpressionPointcut();
// annotationMatchingPointcut();
// nameMatchMethodPointcut();
// jdkRegexpMethodPointcut();
}
/**
* Pointcut
* Pointcut
*/
private static void customPointcut() {
// 创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory(new MyBean());
// 添加切面:使用自定义的切入点和通知构建默认切面
proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new MyCustomPointcut(), new MyCustomAdvice()));
// 获取代理对象
MyBean myBean = (MyBean) proxyFactory.getProxy();
// 使用代理对象调用方法
myBean.getName(); // 将被通知拦截
myBean.getAge(); // 将被通知拦截
myBean.setName(); // 不会被通知拦截
MyCustomPointcut pointcut = new MyCustomPointcut();
showMatchesLog(pointcut);
}
/**
* AspectJExpressionPointcut
* AspectJExpressionPointcut
*/
private static void aspectJExpressionPointcut() {
// 创建 AspectJ 表达式切入点
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* *.getName())");
// 创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory(new MyBean());
// 添加切面:使用自定义的切入点和通知构建默认切面
proxyFactory.addAdvisor(new DefaultPointcutAdvisor(pointcut, new MyCustomAdvice()));
// 获取代理对象
MyBean myBean = (MyBean) proxyFactory.getProxy();
// 使用代理对象调用方法
myBean.getName(); // 将被通知拦截
myBean.getAge(); // 不会被通知拦截
myBean.setName(); // 不会被通知拦截
pointcut.setExpression("execution(* com.xcs.spring.MyService.get*())");
showMatchesLog(pointcut);
}
/**
* AnnotationMatchingPointcut
* AnnotationMatchingPointcut
*/
private static void annotationMatchingPointcut() {
// 创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory(new MyBean());
// 添加切面使用AnnotationMatchingPointcut切入点和通知构建默认切面
proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new AnnotationMatchingPointcut(MyAnnotation.class, false), new MyCustomAdvice()));
// 获取代理对象
MyBean myBean = (MyBean) proxyFactory.getProxy();
// 使用代理对象调用方法
myBean.getName(); // 将被通知拦截
myBean.getAge(); // 将被通知拦截
myBean.setName(); // 将被通知拦截
// 使用AnnotationMatchingPointcut切入点
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(MyClassAnnotation.class, MyMethodAnnotation.class, false);
showMatchesLog(pointcut);
}
/**
* AspectJExpressionPointcut
* AspectJExpressionPointcut
*/
private static void nameMatchMethodPointcut() {
// 创建方法名匹配切入点
// 使用AnnotationMatchingPointcut切入点
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.addMethodName("getAge");
showMatchesLog(pointcut);
}
// 创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory(new MyBean());
// 添加切面:使用自定义的切入点和通知构建默认切面
proxyFactory.addAdvisor(new DefaultPointcutAdvisor(pointcut, new MyCustomAdvice()));
// 获取代理对象
MyBean myBean = (MyBean) proxyFactory.getProxy();
/**
* JdkRegexpMethodPointcut
*/
private static void jdkRegexpMethodPointcut() {
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern(".*set.*");
showMatchesLog(pointcut);
}
// 使用代理对象调用方法
myBean.getName(); // 不会被通知拦截
myBean.getAge(); // 将被通知拦截
myBean.setName(); // 不会被通知拦截
public static void showMatchesLog(Pointcut pointcut) {
try {
Class<MyService> target = MyService.class;
Method getNameMethod = target.getDeclaredMethod("getName");
Method getAgeMethod = target.getDeclaredMethod("getAge");
Method setNameMethod = target.getDeclaredMethod("setName");
ClassFilter classFilter = pointcut.getClassFilter();
MethodMatcher methodMatcher = pointcut.getMethodMatcher();
System.out.println("ClassFilter MyService = " + classFilter.matches(target));
System.out.println("MethodMatcher MyService getName = " + methodMatcher.matches(getNameMethod, target));
System.out.println("MethodMatcher MyService getAge = " + methodMatcher.matches(getAgeMethod, target));
System.out.println("MethodMatcher MyService setName = " + methodMatcher.matches(setNameMethod, target));
} catch (Exception e) {
e.printStackTrace();
}
}
}