ExposeInvocationInterceptor优化

AopContext优化
master
linlei 2024-05-07 11:30:33 +08:00
parent e57e81c9ae
commit 0ada3c6eb5
4 changed files with 35 additions and 38 deletions

View File

@ -7,9 +7,6 @@
- [四、类源码](#四类源码) - [四、类源码](#四类源码)
- [五、最佳实践](#五最佳实践) - [五、最佳实践](#五最佳实践)
- [六、源码分析](#六源码分析) - [六、源码分析](#六源码分析)
- [JDK动态代理拦截器](#jdk动态代理拦截器)
- [CGLIB动态代理拦截器](#cglib动态代理拦截器)
- [七、常见问题](#七常见问题)
### 一、基本信息 ### 一、基本信息
@ -187,7 +184,7 @@ bar...
在Spring AOP框架中无论是在JDK动态代理还是CGLIB动态代理的拦截器中都对`AopContext.setCurrentProxy(proxy)`进行了赋值操作。这个赋值操作的目的是将当前AOP代理对象设置为当前线程的上下文中以便在方法内部可以通过`AopContext.currentProxy()`获取代理对象。 在Spring AOP框架中无论是在JDK动态代理还是CGLIB动态代理的拦截器中都对`AopContext.setCurrentProxy(proxy)`进行了赋值操作。这个赋值操作的目的是将当前AOP代理对象设置为当前线程的上下文中以便在方法内部可以通过`AopContext.currentProxy()`获取代理对象。
#### JDK动态代理拦截器 **JDK动态代理拦截器**
在`org.springframework.aop.framework.JdkDynamicAopProxy#invoke`方法中是JDK动态代理拦截器的一部分。主要处理了AOP代理的上下文。具体来说在方法执行前如果AOP代理对象已经暴露了即`this.advised.exposeProxy`为`true`),则通过`AopContext.setCurrentProxy(proxy)`方法将当前的AOP代理对象设置为当前线程的上下文中以便在方法内部可以通过`AopContext.currentProxy()`来获取代理对象。在方法执行完成后将之前设置的代理对象恢复以保证AOP代理对象的上下文不会影响其他线程。 在`org.springframework.aop.framework.JdkDynamicAopProxy#invoke`方法中是JDK动态代理拦截器的一部分。主要处理了AOP代理的上下文。具体来说在方法执行前如果AOP代理对象已经暴露了即`this.advised.exposeProxy`为`true`),则通过`AopContext.setCurrentProxy(proxy)`方法将当前的AOP代理对象设置为当前线程的上下文中以便在方法内部可以通过`AopContext.currentProxy()`来获取代理对象。在方法执行完成后将之前设置的代理对象恢复以保证AOP代理对象的上下文不会影响其他线程。
@ -217,7 +214,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
} }
``` ```
#### CGLIB动态代理拦截器 **CGLIB动态代理拦截器**
在`org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept`方法中是CGLIB动态代理拦截器的一部分。在方法拦截过程中它主要处理了AOP代理的上下文。具体来说如果AOP代理对象已经暴露了即`this.advised.exposeProxy`为`true`),则通过`AopContext.setCurrentProxy(proxy)`方法将当前的AOP代理对象设置为当前线程的上下文中以便在方法内部可以通过`AopContext.currentProxy()`来获取代理对象。在方法执行完成后将之前设置的代理对象恢复以保证AOP代理对象的上下文不会影响其他线程。 在`org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept`方法中是CGLIB动态代理拦截器的一部分。在方法拦截过程中它主要处理了AOP代理的上下文。具体来说如果AOP代理对象已经暴露了即`this.advised.exposeProxy`为`true`),则通过`AopContext.setCurrentProxy(proxy)`方法将当前的AOP代理对象设置为当前线程的上下文中以便在方法内部可以通过`AopContext.currentProxy()`来获取代理对象。在方法执行完成后将之前设置的代理对象恢复以保证AOP代理对象的上下文不会影响其他线程。
@ -245,15 +242,3 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy
} }
} }
``` ```
### 七、常见问题
1. **代理对象为空问题**
+ 如果在没有启用`exposeProxy`选项的情况下尝试使用`AopContext.currentProxy()`来获取代理对象则可能会导致返回的代理对象为空因为AOP代理对象并未暴露给方法内部。
2. **多线程环境下的线程安全问题**
+ `AopContext`是基于线程的,如果在多线程环境下并发调用了`AopContext.setCurrentProxy(proxy)`和`AopContext.currentProxy()`,可能会出现线程安全问题,因此需要谨慎处理多线程情况。
4. **可读性问题**
+ 在方法内部频繁地使用`AopContext.currentProxy()`来获取代理对象可能会降低代码的可读性,因为会使代码变得复杂,难以理解

View File

@ -6,8 +6,8 @@
- [三、主要功能](#三主要功能) - [三、主要功能](#三主要功能)
- [四、类源码](#四类源码) - [四、类源码](#四类源码)
- [五、最佳实践](#五最佳实践) - [五、最佳实践](#五最佳实践)
- [六、源码分析](#六源码分析) - [六、时序图](#六时序图)
- [七、常见问题](#七常见问题) - [七、源码分析](#七源码分析)
### 一、基本信息 ### 一、基本信息
@ -127,7 +127,7 @@ public final class ExposeInvocationInterceptor implements MethodInterceptor, Pri
### 五、最佳实践 ### 五、最佳实践
创建了一个基于注解的应用程序上下文,从中获取了一个名为 `MyService` 的 bean并调用了其 `doSomething()` 方法。 创建了一个基于注解的应用程序上下文,从中获取了一个名为 `MyService` 的 bean并调用了其 `foo()` 方法。
```java ```java
public class ExposeInvocationInterceptorDemo { public class ExposeInvocationInterceptorDemo {
@ -138,7 +138,7 @@ public class ExposeInvocationInterceptorDemo {
// 从上下文中获取 MyService // 从上下文中获取 MyService
MyService myService = context.getBean(MyService.class); MyService myService = context.getBean(MyService.class);
// 调用方法 // 调用方法
myService.doSomething(); myService.foo();
} }
} }
``` ```
@ -160,8 +160,8 @@ public class AppConfig {
@Service @Service
public class MyService { public class MyService {
public void doSomething() { public void foo() {
System.out.println("Doing something..."); System.out.println("foo...");
} }
} }
``` ```
@ -205,7 +205,25 @@ Proxy Class = class com.xcs.spring.MyService$$EnhancerBySpringCGLIB$$f30643a6
Doing something... Doing something...
``` ```
### 六、源码分析 ### 六、时序图
~~~mermaid
sequenceDiagram
AbstractAutowireCapableBeanFactory->>AbstractAutoProxyCreator: postProcessAfterInitialization()
Note over AbstractAutowireCapableBeanFactory,AbstractAutoProxyCreator: 调用后处理方法
AbstractAutoProxyCreator->>AbstractAutoProxyCreator: wrapIfNecessary()
Note over AbstractAutoProxyCreator: 调用包装方法
AbstractAutoProxyCreator->>AbstractAdvisorAutoProxyCreator: getAdvicesAndAdvisorsForBean()
Note over AbstractAutoProxyCreator,AbstractAdvisorAutoProxyCreator: 获取通知和 Advisors
AbstractAdvisorAutoProxyCreator->>AbstractAdvisorAutoProxyCreator: findEligibleAdvisors()
Note over AbstractAdvisorAutoProxyCreator: 查找合适的 Advisors
AbstractAdvisorAutoProxyCreator->>AspectJAwareAdvisorAutoProxyCreator: extendAdvisors()
Note over AbstractAdvisorAutoProxyCreator,AspectJAwareAdvisorAutoProxyCreator: Advisor 的扩展钩子
AspectJAwareAdvisorAutoProxyCreator->>AspectJProxyUtils:makeAdvisorChainAspectJCapableIfNecessary()
Note over AspectJAwareAdvisorAutoProxyCreator,AspectJProxyUtils: 添加特殊的拦截器
~~~
### 七、源码分析
在`org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator#extendAdvisors`方法中,在开头添加了一个 `ExposeInvocationInterceptor` 在`org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator#extendAdvisors`方法中,在开头添加了一个 `ExposeInvocationInterceptor`
@ -220,7 +238,11 @@ protected void extendAdvisors(List<Advisor> candidateAdvisors) {
} }
``` ```
在`org.springframework.aop.aspectj.AspectJProxyUtils#makeAdvisorChainAspectJCapableIfNecessary`方法中用于在代理链中添加特殊的拦截器以确保与包含AspectJ建议的代理链一起正常工作。具体来说它将 `ExposeInvocationInterceptor` 添加到建议列表的开头。这样做的目的是为了暴露当前Spring AOP调用对于某些AspectJ切点匹配是必要的并使当前AspectJ JoinPoint可用。如果建议链中不存在AspectJ建议则此调用不会产生任何效果。方法返回 `true` 表示成功向建议列表中添加了 `ExposeInvocationInterceptor`,否则返回 `false` 在`org.springframework.aop.aspectj.AspectJProxyUtils#makeAdvisorChainAspectJCapableIfNecessary`
方法中用于在代理链中添加特殊的拦截器以确保与包含AspectJ建议的代理链一起正常工作。具体来说它将 `ExposeInvocationInterceptor`
添加到advisors列表的开头。这样做的目的是为了暴露当前Spring AOP调用对于某些AspectJ切点匹配是必要的并使当前AspectJ
JoinPoint可用。如果advisors链中不存在AspectJ advisor则此调用不会产生任何效果。方法返回 `true`
表示成功向建议列表中添加了 `ExposeInvocationInterceptor`,否则返回 `false`
```java ```java
/** /**
@ -266,13 +288,3 @@ private static boolean isAspectJAdvice(Advisor advisor) {
((PointcutAdvisor) advisor).getPointcut() instanceof AspectJExpressionPointcut)); ((PointcutAdvisor) advisor).getPointcut() instanceof AspectJExpressionPointcut));
} }
``` ```
### 七、常见问题
1. **什么时候应该使用 `ExposeInvocationInterceptor`**
- 当需要在 Spring AOP 中暴露方法调用的完整上下文时,通常使用 `ExposeInvocationInterceptor`。这在一些特定的切面编程场景下是必要的,比如需要在切点中访问方法参数、目标对象或其他方法调用信息时。
2. **为什么调用 `ExposeInvocationInterceptor.currentInvocation()` 抛出异常?**
- 这可能是因为没有在拦截器链的开头正确配置 `ExposeInvocationInterceptor`,或者在不正确的线程上下文中调用了 `currentInvocation()` 方法。另外,如果没有正在进行的 AOP 调用,也会抛出异常。

View File

@ -10,6 +10,6 @@ public class ExposeInvocationInterceptorDemo {
// 从上下文中获取 MyService // 从上下文中获取 MyService
MyService myService = context.getBean(MyService.class); MyService myService = context.getBean(MyService.class);
// 调用方法 // 调用方法
myService.doSomething(); myService.foo();
} }
} }

View File

@ -5,7 +5,7 @@ import org.springframework.stereotype.Service;
@Service @Service
public class MyService { public class MyService {
public void doSomething() { public void foo() {
System.out.println("Doing something..."); System.out.println("foo...");
} }
} }