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();

代理方法执行时会做如下工作

  1. 通过 proxyFactory 的 getInterceptorsAndDynamicInterceptionAdvice() 将其他通知统一转换为 MethodInterceptor 环绕通知
                   AspectJMethodBeforeAdvice
                   AspectJAroundAdvice (环绕通知)
                   AspectJAfterReturningAdvice
                   AspectJAfterThrowingAdvice (环绕通知)
                   AspectJAfterAdvice (环绕通知)
  • 实现了 MethodInterceptor 接口的是环绕通知
  • MethodBeforeAdviceAdapter 将 @Before AspectJMethodBeforeAdvice 适配为 MethodBeforeAdviceInterceptor
  • AfterReturningAdviceAdapter 将 @AfterReturning AspectJAfterReturningAdvice 适配为 AfterReturningAdviceInterceptor
  • 这体现的是适配器设计模式
  1. 所谓静态通知,体现在上面方法的 Interceptors 部分,这些通知调用时无需再次检查切点,直接调用即可
  2. 结合目标与环绕通知链,创建 MethodInvocation 对象,通过它完成整个调用
  3. 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 

收获💡

  1. proceed() 方法调用链中下一个环绕通知
  2. 每个环绕通知内部继续调用 proceed()
  3. 调用到没有更多通知了, 就调用目标方法
  4. Method、Field和Constructor类都继承了AccessibleObject类,它提供了标记反射对象的能力,以抑制在使用时使用默认Java语言访问控制检查,从而能够任意调用被私有化保护的方法、域和构造函数;
示例
        //设置所有的成员都可以访问
        method.setAccessible(true);
        //获取类声明的所有方法
        Method[] methods = clazz.getDeclaredMethods();
        //批量设置类的所有方法都可以被访问
        AccessibleObject.setAccessible(methods, true);

MethodInvocation 的编程技巧在实现拦截器、过滤器时能用上

posted @ 2022-06-24 18:09  xy7112  阅读(50)  评论(0)    收藏  举报