【探索Spring底层】8.手撕jdk代理原理与cglib代理原理

【探索Spring底层】手撕jdk代理原理与cglib代理原理

1. 前言回顾

Spring的AOP实现有两个方式:

  • jdk代理
  • cglib代理

两者不同之处在于

  • jdk要求实现接口,cglib不要求目标实现接口
  • jdk代理与目标之间是平级的兄弟关系,cglib代理与目标之间是父子关系

2. 手撕jdk代理

接口

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

目标

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

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

自定义InvocationHandler接口,里面有三个参数

  • proxy是指代理对象
  • Method是指方法
  • Object是指方法执行需要的参数
public interface InvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

手撕开始!!!

首先新建一个类$Proxy0实现Foo接口

在$Proxy0定义两个方法,通过反射获取到Foo类中的需要增强的两个方法foo和bar

同时还需要在类中定义一个成员变量InvocationHandler

之所以出现这个InvocationHandler(函数式接口),是因为aop增强的内容各不相同,如果写死在代理类中,那么就不能实现动态代理了(也就是说不这样做的话,一个代理类就只能实现一种增强方式,如果需要一百种不同的aop增强,那就需要一百种代理类),所以需要这个函数式接口,定义不同的增强内容

这个类可不简单,是实现动态代理的核心

然后调用InvocationHandler的invoke方法,将代理对象、需要增强的方法、方法的参数作为参数传递过去

public class $Proxy0 implements Foo {
    private InvocationHandler h;

    public $Proxy0(InvocationHandler h) {
        this.h = h1;
    }
    
    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());
        }
    }

    @Override
    public void foo() {
        try {
            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);
        }
    }
}

编写测试代码测试

返回值之所以是Object是因为不是所有的方法返回值是void

为了避免返回值不是void 所以这里设计为Object

public static void main(String[] param) {
    Foo proxy = new $Proxy0(new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
            // 1. 功能增强
            System.out.println("before...");
            // 2. 调用目标
            //方法反射调用
            return method.invoke(new Target(), args);
        }
    });
    proxy.foo();
    proxy.bar();
}

在这里插入图片描述

注意:

method.invoke(" 要调用的方法的名字所隶属的对象实体",方法的参数值);

所以这里需要传递参数Target,因为需要增强的方法属于这个实体类

在完成增强之后的操作之后,就应该输入原来的实体类的对应方法的执行结果,并return


3. jdk反射优化

在前面调用目标的方法的时候是使用反射调用

反射调用效率性能要比正常调用要低,那么jdk代理对此有做什么优化吗?

// 运行时请添加 --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED

/**
 * 前16次是基于java本地api实现的,性能低
 *  代理是在运行期间生成的代理类
 */
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");
    }
}

在这里插入图片描述

由运行结果可以看出,前16次是通过NativeMethodAccessorImpl来反射调用方法的,效率比较低,这是基于java本地api实现的

但是从17次开始,就使用GeneratedMethodAccessor来调用方法了,效率相对较高

之所以效率相对较高,是因这里是直接调用方法,而不是反射调用

当然,完成这一系列的操作也是有代价的,这是借助代理来实现,代理是在运行期间生成的代理类

在这里插入图片描述


4. 手撕cglib代理

在了解了上面的jdk代理之后,想要理解cglib代理就会变得简单不少了

同样的通过一个案例来理解其底层实现

目标

public class Target {
    public void save() {
        System.out.println("save()");
    }

    public void save(int i) {
        System.out.println("save(int)");
    }

    public void save(long j) {
        System.out.println("save(long)");
    }
}

代理类:

简单来说,这里的实现流程如下

首先如果增强的方法是使用反射调用的话

  • 需要获取需要增强的目标对象的各种方法对象Method
  • 代理对类继承目标类,并重写目标类需要增强的方法
  • 使用MethodInterceptor类进行增强(其四个参数分别是指代理类、需要增强的方法对象、方法执行所需参数、方法的代理类)
  • 最后在测试类中通过methodProxy.invoke(target, args)反射调用,在反射之前或者之后就可以进行增强,最后只需要将反射调用的结果retun即可

但是如果增强的方法是使用代理调用的话

  • 不仅需要准备带增强功能的方法,并且还需要准备带原始功能的方法
  • 对每个方法创建其代理对象(MethodProxy.create)
  • 创建方法的代理对象的五个参数分别代表
    1. 需要增强的对象的class
    2. 代理对象的class
    3. 描述–》
      1. ()里面是指带增强方法的参数,如果括号里面什么也没有代表不需要参数;如果括号里面是I,代表参数为int类型;如果括号里面是J,代表是long类型
    4. 带增强功能的方法
    5. 带增强功能的方法对象的其带原始功能的方法
  • 最后在测试类通过methodProxy.invokeSuper(p, args)代理调用方法
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);
        }
    }
}

测试类

public class A13 {

    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        Target target = new Target();
        proxy.setMethodInterceptor(new MethodInterceptor() {
            @Override
            public Object intercept(Object p, Method method, Object[] args,
                                    MethodProxy methodProxy) throws Throwable {
                System.out.println("before...");
//                return method.invoke(target, args); // 反射调用
                // FastClass
//                return methodProxy.invoke(target, args); // 内部无反射, 结合目标用
                return methodProxy.invokeSuper(p, args); // 内部无反射, 结合代理用
            }
        });

        proxy.save();
        proxy.save(1);
        proxy.save(2L);
    }
}

在这里插入图片描述


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

起名方面没有灵感

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值