深入Spring原理-6.动态代理原理分析

简介: 深入Spring原理-6.动态代理原理分析

动态代理


JDK


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

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


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

public class $Proxy1 implements A12.Foo{
    @Override
    public void foo() {
        // 功能增强
        // 这里的增强是无穷无尽的,可能是事务增强
        // 日志增强等等
        System.out.println("before ... ");
        // 调用目标
        /*
        * 包括调用目标的逻辑也不固定
        * 做一个权限检查的增强,满足权限就掉目标
        * 没满足权限就抛出异常,说明权限不足
        * */
        new A12.Target().foo();
        /*
        * 上述所说 其实都是不确定的
        * 那么针对不确定性的设计,就将其设计为抽象的方法
        * 具体真正用到的时候再将抽象的实现提供
        *
        * */
    }
}

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

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

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

System.out.println("before ... ");
new A12.Target().foo();

此时将代码修改成

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

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

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,但是这句代码是写死的。

new A12.Target().foo();

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

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

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

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

以上大体的就设计出来了


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

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


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的实现非常像了

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的用法

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的反射调用了

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

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

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选择代理


@Aspect
    static class MyAspect{
        @Before("execution(* foo())")
        public void before(){
            System.out.println("前置增强");
        }
        @After("execution(* foo())")
        public void before(){
            System.out.println("后置增强");
        }
    }

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

aspect =
    通知1(advice) +  切点1(pointcut)
    通知2(advice) +  切点2(pointcut)
    通知3(advice) +  切点3(pointcut)

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

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

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

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

总结:

ProxyFactory 是用来创建代理的核心实现, 用 AopProxyFactory 选择具体代理实现
    - JdkDynamicAopProxy
    - ObjenesisCglibAopProxy
目录
相关文章
|
20天前
|
Java 数据安全/隐私保护 Spring
Spring进阶:初识动态代理
本文介绍了Spring框架中AOP切面编程的基础——动态代理。通过定义Vehicle接口及其实现类Car和Ship,展示了如何使用动态代理在不修改原代码的基础上增强功能。文章详细解释了动态代理的工作原理,包括通过`Proxy.newProxyInstance()`方法创建代理对象,以及`InvocationHandler`接口中的`invoke()`方法如何处理代理对象的方法调用。最后,通过一个测试类`TestVehicle`演示了动态代理的具体应用。
|
27天前
|
Java Spring 容器
Spring底层原理大致脉络
Spring底层原理大致脉络
|
2月前
|
设计模式 Java 测试技术
spring复习04,静态代理动态代理,AOP
这篇文章讲解了Java代理模式的相关知识,包括静态代理和动态代理(JDK动态代理和CGLIB),以及AOP(面向切面编程)的概念和在Spring框架中的应用。文章还提供了详细的示例代码,演示了如何使用Spring AOP进行方法增强和代理对象的创建。
spring复习04,静态代理动态代理,AOP
|
26天前
|
Java BI API
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
这篇文章介绍了如何在Spring Boot项目中整合iTextPDF库来导出PDF文件,包括写入大文本和HTML代码,并分析了几种常用的Java PDF导出工具。
287 0
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
|
1月前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
118 9
|
1月前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
137 2
|
2月前
|
负载均衡 Java 网络架构
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
85 5
|
2月前
|
XML 存储 Java
Spring-源码深入分析(二)
Spring-源码深入分析(二)
|
1月前
|
XML 前端开发 Java
拼多多1面:聊聊Spring MVC的工作原理!
本文详细剖析了Spring MVC的工作原理,涵盖其架构、工作流程及核心组件。Spring MVC采用MVC设计模式,通过DispatcherServlet、HandlerMapping、Controller和ViewResolver等组件高效处理Web请求。文章还探讨了DispatcherServlet的初始化和请求处理流程,以及HandlerMapping和Controller的角色。通过理解这些核心概念,开发者能更好地构建可维护、可扩展的Web应用。适合面试准备和技术深挖
41 0
|
1月前
|
负载均衡 Java API
Spring Cloud原理详解
Spring Cloud原理详解
62 0