Spring源码-6.动态代理原理分析

  • 👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家
  • 📕系列专栏:Spring源码、JUC源码
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:源码溯源,一探究竟
  • 📝联系方式:nhs19990716,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬👀

动态代理

JDK

java 复制代码
interface Foo {
        void foo();
    }

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

假设我们有这样的接口 和 实现类,那么我们如何还原 JDK的动态代理呢?是分析源码吗?那太枯燥了,而且大概率看不懂,所以还不如本着自己的思想来还原JDK的代理。

比如说最开始我们先来实现这个接口

java 复制代码
public class $Proxy1 implements A12.Foo{

    @Override
    public void foo() {
        // 功能增强
        // 这里的增强是无穷无尽的,可能是事务增强
        // 日志增强等等

        System.out.println("before ... ");
        // 调用目标

        /*
        * 包括调用目标的逻辑也不固定
        * 做一个权限检查的增强,满足权限就掉目标
        * 没满足权限就抛出异常,说明权限不足
        * */
        new A12.Target().foo();


        /*
        * 上述所说 其实都是不确定的
        * 那么针对不确定性的设计,就将其设计为抽象的方法
        * 具体真正用到的时候再将抽象的实现提供
        *
        * */
    }
}

其实问题在注释里也很清晰了,那么应该怎么做呢?

java 复制代码
// 所以提供了一个接口,里面提供了抽象方法
    interface InvocationHandler {
        void invoke();
    }

此时下面两段代码就不用固定在代理类内部了

java 复制代码
System.out.println("before ... ");
new A12.Target().foo();

此时将代码修改成

java 复制代码
public class $Proxy1 implements A12.Foo{

    private A12.InvocationHandler h;

    public $Proxy1(A12.InvocationHandler h){
        this.h = h;
    }

    @Override
    public void foo() {
        h.invoke();
    }
}
// 此时不写在代理类内部了,而是将其抽离出来
public static void main(String[] param) {
        // alt + enter
        Foo proxy1 = new $Proxy1(new InvocationHandler() {
            @Override
            public void invoke() {
				System.out.println("before ... ");
				new A12.Target().foo();
            }
        });
        proxy1.foo();
}

但是将来接口中存在两个方法的时候,就比较尴尬了,就不对了

java 复制代码
interface Foo {
        void foo();
        int bar();
    }

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

        @Override
        public void bar() {
            System.out.println("target bar");
        }
    }

此时调用的时候都是执行invoke,但是这句代码是写死的。

scss 复制代码
new A12.Target().foo();

所以还需要做出改进,就是要判断到底调用的是那个方法?

java 复制代码
public class $Proxy1 implements A12.Foo{

    private A12.InvocationHandler h;

    public $Proxy1(A12.InvocationHandler h){
        this.h = h;
    }

    @Override
    public void foo() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method foo = A12.Foo.class.getDeclaredMethod("foo");
        h.invoke(foo,new Object[0]);
    }
    
    
interface InvocationHandler {
        void invoke(Method method,Object[] args) throws InvocationTargetException, IllegalAccessException;
    }
    
public static void main(String[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        // alt + enter
        Foo proxy1 = new $Proxy1(new InvocationHandler() {
            @Override
            public void invoke(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
                System.out.println("before...");
                method.invoke(new Target(),args);
            }
        });
    
        proxy1.foo();
}

但是上述的改写还是不完美,如果 接口参数中是有返回值的呢?

java 复制代码
interface Foo {
        void foo() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException;
        int bar();
    }

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

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


public int bar(){
        try{
            Method bar = A12.Foo.class.getMethod("bar");
            Object result = h.invoke(bar,new Object[0]);
            // 涉及到通用,具体到那个方法,需要做类型转换
            return (int)result;
        }catch (Throwable e){
            e.printStackTrace();
        }
    }


interface InvocationHandler {
        Object invoke(Method method,Object[] args) throws InvocationTargetException, IllegalAccessException;
    }

public static void main(String[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        // alt + enter
        Foo proxy1 = new $Proxy1(new InvocationHandler() {
            @Override
            public Object invoke(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
                System.out.println("before...");
                return method.invoke(new Target(),args);
            }
        });
        proxy1.foo();

以上大体的就设计出来了

但是真实在设计的过程中会将代理对象传入

java 复制代码
interface InvocationHandler {
        Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
    }
    
    
public class $Proxy0 extends Proxy implements A12.Foo {

    public $Proxy0(InvocationHandler h) {
        super(h);
    }
    @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);
//        }
//    }

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



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

代理一点都不难,无非就是利用了多态、反射的知识

  1. 方法重写可以增强逻辑,只不过这【增强逻辑】千变万化,不能写死在代理内部
  2. 通过接口回调将【增强逻辑】置于代理类之外
  3. 配合接口方法反射(是多态调用),就可以再联动调用目标方法
  4. jdk代理增强是借助多态来实现,因此成员变量、静态方法、final 方法均不能通过jdk代理实现

JDK 动态代理是基于接口来实现代理的,它是通过在运行时创建一个实现了给定接口的代理类的方式来实现的。因此,对于类的成员变量、静态方法以及 final 方法,都无法通过 JDK 动态代理来实现增强。

  1. 成员变量:JDK 动态代理是基于接口的,它只能代理接口中定义的方法,而不能拦截对类成员变量的访问。
  2. 静态方法:代理对象是对接口的实现,而静态方法是属于类的而不是对象,因此无法被代理。
  3. Final 方法:final 方法表示不可重写,而 JDK 动态代理是通过创建目标接口的实现类来实现代理的,无法覆盖 final 方法。

JDK反射优化

其实会将 反射优化成不反射调用了,直接通过类型 . 方法名正常调用了这个方法,并没有反射,但是代价就是因为将反射调用变成正常调用,会生成一个代理类(为了优化一个方法的反射调用,生成了一个代理类)

实现:

前十六次调用走反射,第十七次走的 是反射优化

CGLIB

java 复制代码
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)");
    }
}

cglib的实现是子类继承目标父类

其实现可以说和jdk的实现非常像了

java 复制代码
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);
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }

    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 带增强功能的方法
    @Override
    public void save() {
        try {
            methodInterceptor.intercept(this, save0, new Object[0],null);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

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

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

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); // 反射调用

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

此时讲解methodProxy的用法

java 复制代码
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);
            /*
            * 一共有五个参数
            * 目标类型
            * 代理类型
            * 参数类型和返回值类型Void
            * 带增强功能的方法名
            * 带原始功能的方法名
            * */
            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);
        }
    }
}

而如果使用的话,就可以不用method的反射调用了

java 复制代码
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);
    }

如何避免反射调用?

其内部是使用FastClass。

而FastClass是一个抽象方法,里面有一些关键的方法被实现,比如 getIndex 、 invoke

其中分别对method.invoke(target, args) 和 methodProxy.invoke(target, args) 做出模拟实现,那么思路就会非常的清晰了。

TargetFastClass

java 复制代码
public class TargetFastClass {
    static Signature s0 = new Signature("save", "()V");
    static Signature s1 = new Signature("save", "(I)V");
    static Signature s2 = new Signature("save", "(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 target, Object[] args) {
        if (index == 0) {
            ((Target) target).save();
            return null;
        } else if (index == 1) {
            ((Target) target).save((int) args[0]);
            return null;
        } else if (index == 2) {
            ((Target) target).save((long) args[0]);
            return null;
        } else {
            throw new RuntimeException("无此方法");
        }
    }

    public static void main(String[] args) {
        TargetFastClass fastClass = new TargetFastClass();
        int index = fastClass.getIndex(new Signature("save", "(I)V"));
        System.out.println(index);
        fastClass.invoke(index, new Target(), new Object[]{100});
    }
}

ProxyFastClass

Java 复制代码
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");

    // 获取代理方法的编号
    /*
        Proxy
            saveSuper()              0
            saveSuper(int)           1
            saveSuper(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).saveSuper();
            return null;
        } else if (index == 1) {
            ((Proxy) proxy).saveSuper((int) args[0]);
            return null;
        } else if (index == 2) {
            ((Proxy) proxy).saveSuper((long) args[0]);
            return null;
        } else {
            throw new RuntimeException("无此方法");
        }
    }

    public static void main(String[] args) {
        ProxyFastClass fastClass = new ProxyFastClass();
        int index = fastClass.getIndex(new Signature("saveSuper", "()V"));
        System.out.println(index);

        fastClass.invoke(index, new Proxy(), new Object[0]);
    }
}

如果与jdk的方法反射做对比,jdk并不是一上来就优化,而是先调16次,第17次的时候,针对一个方法会产生一个代理类,代理类能让你的反射变成无需反射,原理和cglib差不多,而cglib是一上来就会产生代理,无需前16次准备工作。

jdk中是一个方法调用就对应一个代理,而cglib是一个代理类会对应两个FastClass,每个FastClass会匹配多个方法。所以代理类的数目会比jdk的要少很多。

Spring选择代理

java 复制代码
@Aspect
    static class MyAspect{
        @Before("execution(* foo())")
        public void before(){
            System.out.println("前置增强");
        }

        @After("execution(* foo())")
        public void before(){
            System.out.println("后置增强");
        }
    }

上面是我们常见的aspect切面的概念,其组成主要是

scss 复制代码
aspect =
    通知1(advice) +  切点1(pointcut)
    通知2(advice) +  切点2(pointcut)
    通知3(advice) +  切点3(pointcut)

而还有一个更深层次的切面

ini 复制代码
advisor = 更细粒度的切面,包含一个通知和切点
最终aspect生效的时候会被拆解成多个 advisor

所以如果来模拟 一个 advisor实现的话可以这样

java 复制代码
public static void main(String[] args) {
        /*
            两个切面概念
            aspect =
                通知1(advice) +  切点1(pointcut)
                通知2(advice) +  切点2(pointcut)
                通知3(advice) +  切点3(pointcut)
                ...
            advisor = 更细粒度的切面,包含一个通知和切点

            最终aspect生效的时候会被拆解成多个 advisor
         */

        // 1. 备好切点
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* foo())");
        // 最基本也是最重要的通知,其它通知都会转换成这个来执行
        // 2. 备好通知
        MethodInterceptor advice = invocation -> {
            System.out.println("before...");
            Object result = invocation.proceed(); // 调用目标
            System.out.println("after...");
            return result;
        };
        // 3. 备好切面
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);

        /*
           4. 创建代理
                a. proxyTargetClass = false, 目标实现了接口, 用 jdk 实现
                b. proxyTargetClass = false,  目标没有实现接口, 用 cglib 实现
                c. proxyTargetClass = true, 总是使用 cglib 实现
         */
        Target2 target = new Target2();
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(target);
        factory.addAdvisor(advisor);
        factory.setInterfaces(target.getClass().getInterfaces());
        factory.setProxyTargetClass(false);
        Target2 proxy = (Target2) factory.getProxy();
        System.out.println(proxy.getClass());
        proxy.foo();
        proxy.bar();
        
    }

    interface I1 {
        void foo();

        void bar();
    }

    static class Target1 implements I1 {
        public void foo() {
            System.out.println("target1 foo");
        }

        public void bar() {
            System.out.println("target1 bar");
        }
    }

    static class Target2 {
        public void foo() {
            System.out.println("target2 foo");
        }

        public void bar() {
            System.out.println("target2 bar");
        }
    }

总结:

markdown 复制代码
ProxyFactory 是用来创建代理的核心实现, 用 AopProxyFactory 选择具体代理实现
    - JdkDynamicAopProxy
    - ObjenesisCglibAopProxy
相关推荐
颜淡慕潇1 小时前
【K8S问题系列 |1 】Kubernetes 中 NodePort 类型的 Service 无法访问【已解决】
后端·云原生·容器·kubernetes·问题解决
尘浮生2 小时前
Java项目实战II基于Spring Boot的光影视频平台(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·maven·intellij-idea
尚学教辅学习资料2 小时前
基于SpringBoot的医药管理系统+LW示例参考
java·spring boot·后端·java毕业设计·医药管理
monkey_meng3 小时前
【Rust中的迭代器】
开发语言·后端·rust
余衫马3 小时前
Rust-Trait 特征编程
开发语言·后端·rust
monkey_meng3 小时前
【Rust中多线程同步机制】
开发语言·redis·后端·rust
paopaokaka_luck7 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
码农小旋风9 小时前
详解K8S--声明式API
后端
Peter_chq9 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml49 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍