Spring Aop 的实现

一.我们都知道spring的aop是由动态代理来实现的,而动态代理一般有2种实现jdk自带的和cglib动态代理。

1.先用代码分别演示一下2种实现:

基于jdk的

public class JdkProxyDemo {

    interface Foo {
        void foo();
    }

    static class Target implements Foo {
        public void foo() {
            System.out.println("target foo");
        }
    }

    public static void main(String[] param) {
        // 目标对象
        Target target = new Target();
        // 代理对象
        Foo proxy = (Foo) Proxy.newProxyInstance(
                Target.class.getClassLoader(), new Class[]{Foo.class},
                (p, method, args) -> {
                    System.out.println("proxy before...");
                    Object result = method.invoke(target, args);
                    System.out.println("proxy after...");
                    return result;
                });
        // 调用代理
        proxy.foo();
    }
}

  • jdk 动态代理要求目标必须实现接口,生成的代理类实现相同接口,因此代理与目标之间是平级兄弟关系

基于cglib的

public class CglibProxyDemo {

    static class Target {
        public void foo() {
            System.out.println("target foo");
        }
    }

    public static void main(String[] param) {
        // 目标对象
        Target target = new Target();
        // 代理对象
        Target proxy = (Target) Enhancer.create(Target.class, 
                (MethodInterceptor) (p, method, args, methodProxy) -> {
            System.out.println("proxy before...");
            Object result = methodProxy.invoke(target, args);
            // 另一种调用方法,不需要目标对象实例
//            Object result = methodProxy.invokeSuper(p, args);
            System.out.println("proxy after...");
            return result;
        });
        // 调用代理
        proxy.foo();
    }
}
  • cglib 不要求目标实现接口,它生成的代理类是目标的子类,因此代理与目标之间是子父关系

  • 限制⛔:根据上述分析 final 类无法被 cglib 增强

 2.接下来模拟jdk的动态代理实现

接口、目标定义:

interface Foo {
        void foo();
        int bar();
    }

    static class Target implements Foo {
        public void foo() {
            System.out.println("target foo");
        }

        public int bar() {
            System.out.println("target bar");
            return 100;
        }
    }

    public static void main(String[] param) {
        // ⬇️1. 创建代理,这时传入 InvocationHandler
        Foo proxy = new $Proxy0(new InvocationHandler() {    
            // ⬇️5. 进入 InvocationHandler
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
                // ⬇️6. 功能增强
                System.out.println("before...");
                // ⬇️7. 反射调用目标方法
                return method.invoke(new Target(), args);
            }
        });
        // ⬇️2. 调用代理方法
        proxy.foo();
        proxy.bar();
    }

 代理实现:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

// ⬇️这就是 jdk 代理类的源码, 秘密都在里面
public class $Proxy0 extends Proxy implements A12.Foo {

    public $Proxy0(InvocationHandler h) {
        super(h);
    }
    // ⬇️3. 进入代理方法
    public void foo() {
        try {
            // ⬇️4. 回调 InvocationHandler
            h.invoke(this, foo, new Object[0]);
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public int bar() {
        try {
            Object result = h.invoke(this, bar, new Object[0]);
            return (int) result;
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    static Method foo;
    static Method bar;
    static {
        try {
            foo = A12.Foo.class.getMethod("foo");
            bar = A12.Foo.class.getMethod("bar");
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }
}
  1. 方法重写可以增强逻辑,只不过这【增强逻辑】千变万化,不能写死在代理内部

  2. 通过接口回调将【增强逻辑】置于代理类之外

  3. 配合接口方法反射(是多态调用),就可以再联动调用目标方法

  4. 会用 arthas 的 jad 工具反编译代理类

  5. 限制⛔:代理增强是借助多态来实现,因此成员变量、静态方法、final 方法均不能通过代理实现

         其实本质上就是采用InvocationHandler接口使用其的匿名内部类,给出相对应的加强方法。同时这个动态代理类其实本质上并不是实际存在的文件而是spring根据asm技术(字节码生成技术)来动态生成的,动态加载的。我们可以用阿里开源的arthas来观察。

        3.另外就是jdk自带代理的反射调用优化了

 因为反射调用其实是非常有损性能的(反射包括了一些动态类型,所以JVM无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被 执行的代码或对性能要求很高的程序中使用反射。)jdk对反射的反复调用进行了优化:

  1. 前 16 次反射性能较低

  2. 第 17 次调用会生成代理类,优化为非反射调用

  3. 会用 arthas 的 jad 工具反编译第 17 次调用生成的代理类

可以用下面的代码进行测试

package com.itheima.a12;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

// 运行时请添加 --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED
public class TestMethodInvoke {
    public static void main(String[] args) throws Exception {
        Method foo = TestMethodInvoke.class.getMethod("foo", int.class);
        for (int i = 1; i <= 17; i++) {
            show(i, foo);
            foo.invoke(null, i);
        }
        System.in.read();
    }

    // 方法反射调用时, 底层 MethodAccessor 的实现类
    private static void show(int i, Method foo) throws Exception {
        Method getMethodAccessor = Method.class.getDeclaredMethod("getMethodAccessor");
        getMethodAccessor.setAccessible(true);
        Object invoke = getMethodAccessor.invoke(foo);
        if (invoke == null) {
            System.out.println(i + ":" + null);
            return;
        }
        Field delegate = Class.forName("jdk.internal.reflect.DelegatingMethodAccessorImpl").getDeclaredField("delegate");
        delegate.setAccessible(true);
        System.out.println(i + ":" + delegate.get(invoke));
    }

    public static void foo(int i) {
        System.out.println(i + ":" + "foo");
    }
}

4.cglib代理实现

target类

public class Target {
    public void save(){
        System.out.println("save");
    }
    public void save(int i){
        System.out.println("saveInt" + i);
    }
    public void save(long j){
        System.out.println("saveLong" + j);
    }
}

代理类实现 

public class Proxy extends Target {

    private MethodInterceptor methodInterceptor;

    public void setMethodInterceptor(MethodInterceptor methodInterceptor) {
        this.methodInterceptor = methodInterceptor;
    }

    static Method save0;
    static Method save1;
    static Method save2;
    static MethodProxy save0Proxy;
    static MethodProxy save1Proxy;
    static MethodProxy save2Proxy;

    static {
        try {
            save0 = Target.class.getMethod("save");
            save1 = Target.class.getMethod("save", int.class);
            save2 = Target.class.getMethod("save", long.class);    // (内部是参数类型)外面是返回值类型
            save0Proxy = MethodProxy.create(Target.class,Proxy.class,"()V","save","saveSuper");
            save1Proxy = MethodProxy.create(Target.class,Proxy.class,"(I)V","save","saveSuper");
            save2Proxy = MethodProxy.create(Target.class,Proxy.class,"(J)V","save","saveSuper");
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }

    public void saveSuper() {
        super.save();
    }

    public void saveSuper(int i) {
        super.save(i);
    }

    public void saveSuper(long j) {
        super.save(j);
    }

    @Override
    public void save() {
        try {
            methodInterceptor.intercept(this, save0, new Object[0], save0Proxy);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public void save(int i) {
        try {
            methodInterceptor.intercept(this, save1, new Object[]{i}, save1Proxy);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public void save(long j) {
        try {
            methodInterceptor.intercept(this, save2, new Object[]{j}, save2Proxy);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }
}

MethodProxy实现:基于Fastclass

public class ProxyFastClass {
    static Signature s0 = new Signature("saveSuper", "()V");
    static Signature s1 = new Signature("saveSuper", "(I)V");
    static Signature s2 = new Signature("saveSuper", "(J)V");

    // 获取目标方法的编号
    /*
        Target
            save()              0
            save(int)           1
            save(long)          2
        signature 包括方法名字、参数返回值
     */
    public int getIndex(Signature signature) {
        if (s0.equals(signature)) {
            return 0;
        } else if (s1.equals(signature)) {
            return 1;
        } else if (s2.equals(signature)) {
            return 2;
        }
        return -1;
    }

    // 根据方法编号, 正常调用目标对象方法
    public Object invoke(int index, Object proxy, Object[] args) {
        if (index == 0) {
            ((Proxy) proxy).save();
        } else if (index == 1) {
            int i = (int) args[0];
            ((Proxy) proxy).save(i);
        } else if (index == 2) {
            long i = (long) args[0];
            ((Proxy) proxy).save(i);
        }else {
            throw new RuntimeException();
        }
        return null;
    }
}

  1. 当调用 MethodProxy 的 invoke 或 invokeSuper 方法时, 会动态生成两个类

    • ProxyFastClass 配合代理对象一起使用, 避免反射

    • TargetFastClass 配合目标对象一起使用, 避免反射 (Spring 用的这种)

  2. TargetFastClass 记录了 Target 中方法与编号的对应关系

    • save(long) 编号 2

    • save(int) 编号 1

    • save() 编号 0

    • 首先根据方法名和参数个数、类型, 用 switch 和 if 找到这些方法编号

    • 然后再根据编号去调用目标方法, 又用了一大堆 switch 和 if, 但避免了反射

  3. ProxyFastClass 记录了 Proxy 中方法与编号的对应关系,不过 Proxy 额外提供了下面几个方法

    • saveSuper(long) 编号 2,不增强,仅是调用 super.save(long)

    • saveSuper(int) 编号 1,不增强, 仅是调用 super.save(int)

    • saveSuper() 编号 0,不增强, 仅是调用 super.save()

    • 查找方式与 TargetFastClass 类似

  4. 为什么有这么麻烦的一套东西呢?

    • 避免反射, 提高性能, 代价是一个代理类配两个 FastClass 类, 代理类中还得增加仅调用 super 的一堆方法

    • 用编号处理方法对应关系比较省内存, 另外, 最初获得方法顺序是不确定的, 这个过程没法固定死

二:另外的可能aop实现

1.ajc 编译器

通过 ajc 编译器在编译 class 类文件时,就把通知的增强功能,织入到目标类的字节码中

  • 编译器也能修改 class 实现增强

  • 编译器增强能突破代理仅能通过方法重写增强的限制:可以对构造方法、静态方法等实现增强

优点:

  • aspectj 在编译和加载时,修改目标字节码,性能较高

  • aspectj 因为不用代理,能突破一些技术上的限制,例如对构造、对静态方法、对 final 也能增强

缺点: 

  •  但 aspectj 侵入性较强,且需要学习新的 aspectj 特有语法,因此没有广泛流行

 2.agent 类加载

类加载时可以通过 agent 修改 class 实现增强

三:spring采用jdk和cglib相结合的实现

        首先aop的包括切点(接口为Pointcut,典型实现 AspectJExpressionPointcut )和通知(典型接口为 MethodInterceptor 代表环绕通知 ),将切点和通知组合就形成了切面(接口为Advisor ),然后采用代理工厂来根据切面生成代理,流程如下:

  • AopProxyFactory 根据 proxyTargetClass 等设置选择 AopProxy 实现

  • AopProxy 通过 getProxy 创建代理对象

  • 图中 Proxy 都实现了 Advised 接口,能够获得关联的切面集合与目标(其实是从 ProxyFactory 取得)

  • 调用代理方法时,会借助 ProxyFactory 将通知统一转为环绕通知:MethodInterceptor

 

于是代理的实现选择有如下规则:

ProxyFactory 用来创建代理

  • 如果指定了接口,且 proxyTargetClass = false,使用 JdkDynamicAopProxy

  • 如果没有指定接口,或者 proxyTargetClass = true,使用 ObjenesisCglibAopProxy

  • 例外:如果目标是接口类型或已经是 Jdk 代理,使用 JdkDynamicAopProxy

四:代理需要注意的地方

1.spring 代理的设计特点

        依赖注入和初始化影响的是原始对象。因此 cglib 不能用 MethodProxy.invokeSuper()

代理与目标是两个对象,二者成员变量并不共用数据

2.static 方法、final 方法、private 方法均无法增强

进一步理解代理增强基于方法重写

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值