从静态代理到动态代理,理解动态代理原理

前提

已经明白静态代理,也就是普通的代理模式,是建立一个代理类,与目标类实现同一个接口,在方法中调用目标实例的相应方法,并且在前后加上增强代码。

静态代理的问题就是:1. 每个目标类都需要手动构建一个代理类,工作量大;2. 增强逻辑(比如日志)可以提出来加以复用。
所以动态代理要解决的就是,在给定一个目标类的情况下,直接创建一个代理实例(不需要创建代理类),代理实例里面是统一的一个增强逻辑。

动态代理的实现

要直接根据目标类给出一个代理实例,主要有两种思路:

  1. 使用目标类的信息,继承这个目标类作为代理类,得到的代理实例。(CGLib动态代理)
  2. 使用目标类的接口的信息,运用反射复制一份接口类的对象,得到目标类的代理实例。(jdk的proxy动态代理)

综上可知:

  1. CGLib的目标类不需要有实现的接口,并且如果目标类是final,则不可使用CGLib代理;
  2. JDK代理目标类必须实现了接口,否则不可使用proxy代理;

另外JDK的proxy代理是jdk自带的,CGLib需要引入第三方包。在spring中二者都有视情况而使用。

CGLib动态代理

CGLIB基于ASM(一个短小精悍的字节码操作框架)直接读取目标类的class文件操作字节码,然后生成一个子类作为代理对象。
速度上说是CGLIB代理对字节码操作,速度比JDK用反射快。不过主要还是解决目标类没有实现接口的情况。
简单记录下概念,具体原理略过。

JDK动态代理

对于接口和接口的实现类来说,用反射得到的信息只差一个构造方法。比如从CalculatorImpl的类对象可以获取到构造方法、add()、subtract()方法,从其接口Calculator的类对象中可以获取到add()、subtract()方法(接口不能获取到构造方法)。

不可能直接给接口Calculator插入一个构造方法,**所以JDK动态代理的解决办法就是,拷贝接口的类对象并赋予其构造方法,然后从这个类对象中构建实例。**也就是说,最总生成的代理实例,没有类而只有内存中的类对象。

比如要创建一个CalculatorImpl的代理类,JDK动态代理的做法就是:

  1. 得到CalculatorImpl实现的接口Calculator;
  2. 通过反射得到接口Calculator的类对象Class<Calculator> calculatorClass;
  3. 拷贝calculatorClass生成另一个类对象,并赋予构造方法得到Class<newCalculator> newClass;
  4. 用newClass构建对象,这个对象就是CalculatorImpl的代理。

和静态代理的区别:静态代理是构建一个代理类,然后从代理类正常创建一个代理实例;动态代理是用接口的Class对象构建一个新的Class对象,然后用这个新的Class对象创建一个代理实例。前者要手动编写代理类,手动实例化代理类,后者是根据传入接口动态形成类对象,自动生成代理实例。

proxy api

Proxy类有四个静态方法
在这里插入图片描述

  • getProxyClass: 传入类加载器,接口Class对象,获得代理类的Class对象(类加载器获取方法:该class对象.getClassLoader())
  • isProxyClass: 判断是否代理类
  • getInvocationHandler:传入代理实例,得到其调用处理器。这个调用处理器就是增强代码的执行器,原理是通过反射体系中,Method的invoke方法去执行增强代码。在获取目标类的代理实例过程中,需要创建一个InvocationHandler对象并实现其中的invoke方法,内容就是代理实例的增强代码。
  • newProxyInstance:最方便常用的api,传入类加载器、接口Class对象、调用处理器,直接得到代理实例。
proxy构建一个代理实例的步骤
最简版:
  1. 实例化InvocationHandler,实现 invoke 方法(增强代码+目标对象的方法反射调用位置);
  2. 通过 Proxy 的静态方法 newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理;
  3. 通过代理调用要执行的方法。
复杂版(其实是newProxyInstance隐藏的细节):
  1. 实例化InvocationHandler,实现 invoke 方法(增强代码+目标对象的方法反射调用位置);
  2. 通过getProxyClass得到目标接口的代理类Class对象;
  3. 调用代理类对象的getConstructor方法(传入InvocationHandler.class)得到代理类的构造器
  4. 调用代理类构造器newInstance方法(传入第一步得到的InvocationHandler实例),得到代理实例
  5. 通过代理调用要执行的方法。

InvocationHandler实例invoke方法里实现的是用户自定义的,如果只包含增强代码,那么调用代理实例的某方法时就不会调用目标对象的方法,所以实际使用的时候需要得到目标对象,再将目标对象放在invoke内的合适位置进行反射调用。

工程启发

我们在建立工程编写代码的时候,经常要有个service层和一个serviceImpl层,定义接口与实现类,这个做法出了一般而言的代码解耦等作用外,从动态代理的角度出发,似乎还方便使用JDK代理来实现动态代理,因为JDK动态代理是原生自带并且需要目标类实现接口。
不过实际去打印controller层的service实例时发现也有CGLib代理,不知道spring是怎么调度的,和版本是否有关系。虽然这个层次设计不一定能方便框架的动态代理选择,不过原理上看二者还是有所联系的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值