spring 17 静态通知调用
静态通知调用
代理对象调用流程如下(以 JDK 动态代理实现为例)
- 从 ProxyFactory 获得 Target 和环绕通知链,根据他俩创建 MethodInvocation,简称 mi
- 首次执行 mi.proceed() 发现有下一个环绕通知,调用它的 invoke(mi)
- 进入环绕通知1,执行前增强,再次调用 mi.proceed() 发现有下一个环绕通知,调用它的 invoke(mi)
- 进入环绕通知2,执行前增强,调用 mi.proceed() 发现没有环绕通知,调用 mi.invokeJoinPoint() 执行目标方法
- 目标方法执行结束,将结果返回给环绕通知2,执行环绕通知2 的后增强
- 环绕通知2继续将结果返回给环绕通知1,执行环绕通知1 的后增强
- 环绕通知1返回最终的结果
图中不同颜色对应一次环绕通知或目标的调用起始至终结
点击查看
sequenceDiagram
participant Proxy
participant ih as InvocationHandler
participant mi as MethodInvocation
participant Factory as ProxyFactory
participant mi1 as MethodInterceptor1
participant mi2 as MethodInterceptor2
participant Target
Proxy ->> +ih : invoke()
ih ->> +Factory : 获得 Target
Factory -->> -ih :
ih ->> +Factory : 获得 MethodInterceptor 链
Factory -->> -ih :
ih ->> +mi : 创建 mi
mi -->> -ih :
rect rgb(200, 223, 255)
ih ->> +mi : mi.proceed()
mi ->> +mi1 : invoke(mi)
mi1 ->> mi1 : 前增强
rect rgb(200, 190, 255)
mi1 ->> mi : mi.proceed()
mi ->> +mi2 : invoke(mi)
mi2 ->> mi2 : 前增强
rect rgb(150, 190, 155)
mi2 ->> mi : mi.proceed()
mi ->> +Target : mi.invokeJoinPoint()
Target ->> Target :
Target -->> -mi2 : 结果
end
mi2 ->> mi2 : 后增强
mi2 -->> -mi1 : 结果
end
mi1 ->> mi1 : 后增强
mi1 -->> -mi : 结果
mi -->> -ih :
end
ih -->> -Proxy :
通知调用过程
点击查看代码
AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
// 1. 高级切面转低级切面类
List<Advisor> list = new ArrayList<>();
for (Method method : Aspect.class.getDeclaredMethods()) {
if (method.isAnnotationPresent(Before.class)) {
// 解析切点
String expression = method.getAnnotation(Before.class).value();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(expression);
// 通知类
AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);
// 切面
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
list.add(advisor);
} else if (method.isAnnotationPresent(AfterReturning.class)) {
// 解析切点
String expression = method.getAnnotation(AfterReturning.class).value();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(expression);
// 通知类
AspectJAfterReturningAdvice advice = new AspectJAfterReturningAdvice(method, pointcut, factory);
// 切面
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
list.add(advisor);
} else if (method.isAnnotationPresent(Around.class)) {
// 解析切点
String expression = method.getAnnotation(Around.class).value();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(expression);
// 通知类
AspectJAroundAdvice advice = new AspectJAroundAdvice(method, pointcut, factory);
// 切面
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
list.add(advisor);
}
}
for (Advisor advisor : list) {
System.out.println(advisor);
}
// 2. 通知统一转换为环绕通知 MethodInterceptor
Target target = new Target();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvice(ExposeInvocationInterceptor.INSTANCE); // 准备把 MethodInvocation 放入当前线程
proxyFactory.addAdvisors(list);
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
// 将通知都转换为环绕通知 MethodInterceptor,已经是环绕通知的不需要转换
List<Object> methodInterceptorList =
proxyFactory.getInterceptorsAndDynamicInterceptionAdvice(Target.class.getMethod("foo"), Target.class);
for (Object o : methodInterceptorList) {
System.out.println(o);
}
// 3. 创建并执行调用链 (环绕通知s + 目标),因为一些通知运行时需要调用链,所有需要将其放入线程中。
MethodInvocation methodInvocation = new ReflectiveMethodInvocation(
null, target, Target.class.getMethod("foo"), new Object[0], Target.class, methodInterceptorList
);
methodInvocation.proceed();
代理方法执行时会做如下工作
- 通过 proxyFactory 的 getInterceptorsAndDynamicInterceptionAdvice() 将其他通知统一转换为 MethodInterceptor 环绕通知
AspectJMethodBeforeAdvice
AspectJAroundAdvice (环绕通知)
AspectJAfterReturningAdvice
AspectJAfterThrowingAdvice (环绕通知)
AspectJAfterAdvice (环绕通知)
- 实现了 MethodInterceptor 接口的是环绕通知
- MethodBeforeAdviceAdapter 将 @Before AspectJMethodBeforeAdvice 适配为 MethodBeforeAdviceInterceptor
- AfterReturningAdviceAdapter 将 @AfterReturning AspectJAfterReturningAdvice 适配为 AfterReturningAdviceInterceptor
- 这体现的是适配器设计模式
- 所谓静态通知,体现在上面方法的 Interceptors 部分,这些通知调用时无需再次检查切点,直接调用即可
- 结合目标与环绕通知链,创建 MethodInvocation 对象,通过它完成整个调用
- proxyFactory.addAdvice(ExposeInvocationInterceptor.INSTANCE); // 准备把 MethodInvocation 放入当前线程,底层借助 ThreadLocal
- 为了让某些通知运行时可以使用到调用链,所以用另一个环绕通知 ExposeInvocationInterceptor(spring 提供)将其放到最外层。需要第一个添加
模拟 MethodInvocation
点击查看代码
/**
* 模拟 MethodInvocation 的调用链
* proceed() 方法执行流程
* 1.调用 advice1 的 invoke,内部调用了proceed 实现了递归
* 2.调用 advice2 的 invoke,内部调用了proceed
* 3.反射调用目标方法,返回结果传递给 advice2 的 invoke 方法返回结果
* 4.advice2 的返回结果传递给 advice1 的返回结果
* 5.advice1 将返回结果返回。如果返回结果在其中没有改变,依然还是目标方法执行的返回结果。
*/
public class S17 {
public static void main(String[] args) throws Throwable {
S17Advice1 advice1 = new S17Advice1();
S17Advice2 advice2 = new S17Advice2();
List<MethodInterceptor> advices = new ArrayList<>();
advices.add(advice1);
advices.add(advice2);
S17MethodInvocation methodInvocation =
new S17MethodInvocation(S17Bean1.class.getMethod("s17A",int.class ), new Object[]{1}, new S17Bean1(), advices);
methodInvocation.proceed();
}
static class S17Bean1{
public void s17A(int i){
System.out.println("s17A running...");
}
}
static class S17Advice1 implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("S17Advice1 before...");
Object result = invocation.proceed();
System.out.println("S17Advice1 after...");
return result;
}
}
static class S17Advice2 implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("S17Advice2 before...");
Object result = invocation.proceed();
System.out.println("S17Advice2 after...");
return result;
}
}
static class S17MethodInvocation implements MethodInvocation{
private Method method; //目标方法
private Object[] args; //方法参数
private Object target; //目标对象
private List<MethodInterceptor> advices; //环绕通知集合
private int count = 1; //调用次数
public S17MethodInvocation(Method method, Object[] args, Object target, List<MethodInterceptor> advices) {
this.method = method;
this.args = args;
this.target = target;
this.advices = advices;
}
@Override
public Method getMethod() {
return method;
}
@Override
public Object[] getArguments() {
return args;
}
//责任链模式
@Override
public Object proceed() throws Throwable {
//判断调用次数是否和通知数相等
if (count > advices.size()) {
//次数到了调用目标方法并返回结束递归
return method.invoke(target, args);
}
//次数没到,递归调用通知
MethodInterceptor methodInterceptor = advices.get(count++ - 1);
return methodInterceptor.invoke(this);
}
@Override
public Object getThis() {
return target;
}
@Override
public AccessibleObject getStaticPart() {
return method;
}
}
}
/**
AccessibleObject是Method、Field、Constructor类的基类,它提供了将反射的对象标记为在使用的时候取消默认Java语言访问控制检查力,对于公共成员、默认
成员、私有成员、受保护成员;在分别使用Field、Method、Constructor对象来设置或获取字段、调用方法,或者创建初始化对象的时候,执行安全检查。
**/
public class AccessibleObject implements AnnotatedElement
收获💡
- proceed() 方法调用链中下一个环绕通知
- 每个环绕通知内部继续调用 proceed()
- 调用到没有更多通知了, 就调用目标方法
- Method、Field和Constructor类都继承了AccessibleObject类,它提供了标记反射对象的能力,以抑制在使用时使用默认Java语言访问控制检查,从而能够任意调用被私有化保护的方法、域和构造函数;
示例
//设置所有的成员都可以访问
method.setAccessible(true);
//获取类声明的所有方法
Method[] methods = clazz.getDeclaredMethods();
//批量设置类的所有方法都可以被访问
AccessibleObject.setAccessible(methods, true);
MethodInvocation 的编程技巧在实现拦截器、过滤器时能用上