前提
已经明白静态代理,也就是普通的代理模式,是建立一个代理类,与目标类实现同一个接口,在方法中调用目标实例的相应方法,并且在前后加上增强代码。
静态代理的问题就是:1. 每个目标类都需要手动构建一个代理类,工作量大;2. 增强逻辑(比如日志)可以提出来加以复用。
所以动态代理要解决的就是,在给定一个目标类的情况下,直接创建一个代理实例(不需要创建代理类),代理实例里面是统一的一个增强逻辑。
动态代理的实现
要直接根据目标类给出一个代理实例,主要有两种思路:
- 使用目标类的信息,继承这个目标类作为代理类,得到的代理实例。(CGLib动态代理)
- 使用目标类的接口的信息,运用反射复制一份接口类的对象,得到目标类的代理实例。(jdk的proxy动态代理)
综上可知:
- CGLib的目标类不需要有实现的接口,并且如果目标类是final,则不可使用CGLib代理;
- JDK代理目标类必须实现了接口,否则不可使用proxy代理;
另外JDK的proxy代理是jdk自带的,CGLib需要引入第三方包。在spring中二者都有视情况而使用。
CGLib动态代理
CGLIB基于ASM(一个短小精悍的字节码操作框架)直接读取目标类的class文件操作字节码,然后生成一个子类作为代理对象。
速度上说是CGLIB代理对字节码操作,速度比JDK用反射快。不过主要还是解决目标类没有实现接口的情况。
简单记录下概念,具体原理略过。
JDK动态代理
对于接口和接口的实现类来说,用反射得到的信息只差一个构造方法。比如从CalculatorImpl的类对象可以获取到构造方法、add()、subtract()方法,从其接口Calculator的类对象中可以获取到add()、subtract()方法(接口不能获取到构造方法)。
不可能直接给接口Calculator插入一个构造方法,**所以JDK动态代理的解决办法就是,拷贝接口的类对象并赋予其构造方法,然后从这个类对象中构建实例。**也就是说,最总生成的代理实例,没有类而只有内存中的类对象。
比如要创建一个CalculatorImpl的代理类,JDK动态代理的做法就是:
- 得到CalculatorImpl实现的接口Calculator;
- 通过反射得到接口Calculator的类对象Class<Calculator> calculatorClass;
- 拷贝calculatorClass生成另一个类对象,并赋予构造方法得到Class<newCalculator> newClass;
- 用newClass构建对象,这个对象就是CalculatorImpl的代理。
和静态代理的区别:静态代理是构建一个代理类,然后从代理类正常创建一个代理实例;动态代理是用接口的Class对象构建一个新的Class对象,然后用这个新的Class对象创建一个代理实例。前者要手动编写代理类,手动实例化代理类,后者是根据传入接口动态形成类对象,自动生成代理实例。
proxy api
Proxy类有四个静态方法
- getProxyClass: 传入类加载器,接口Class对象,获得代理类的Class对象(类加载器获取方法:该class对象.getClassLoader())
- isProxyClass: 判断是否代理类
- getInvocationHandler:传入代理实例,得到其调用处理器。这个调用处理器就是增强代码的执行器,原理是通过反射体系中,Method的invoke方法去执行增强代码。在获取目标类的代理实例过程中,需要创建一个InvocationHandler对象并实现其中的invoke方法,内容就是代理实例的增强代码。
- newProxyInstance:最方便常用的api,传入类加载器、接口Class对象、调用处理器,直接得到代理实例。
proxy构建一个代理实例的步骤
最简版:
- 实例化InvocationHandler,实现 invoke 方法(增强代码+目标对象的方法反射调用位置);
- 通过 Proxy 的静态方法 newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理;
- 通过代理调用要执行的方法。
复杂版(其实是newProxyInstance隐藏的细节):
- 实例化InvocationHandler,实现 invoke 方法(增强代码+目标对象的方法反射调用位置);
- 通过getProxyClass得到目标接口的代理类Class对象;
- 调用代理类对象的getConstructor方法(传入InvocationHandler.class)得到代理类的构造器
- 调用代理类构造器newInstance方法(传入第一步得到的InvocationHandler实例),得到代理实例
- 通过代理调用要执行的方法。
InvocationHandler实例invoke方法里实现的是用户自定义的,如果只包含增强代码,那么调用代理实例的某方法时就不会调用目标对象的方法,所以实际使用的时候需要得到目标对象,再将目标对象放在invoke内的合适位置进行反射调用。
工程启发
我们在建立工程编写代码的时候,经常要有个service层和一个serviceImpl层,定义接口与实现类,这个做法出了一般而言的代码解耦等作用外,从动态代理的角度出发,似乎还方便使用JDK代理来实现动态代理,因为JDK动态代理是原生自带并且需要目标类实现接口。
不过实际去打印controller层的service实例时发现也有CGLib代理,不知道spring是怎么调度的,和版本是否有关系。虽然这个层次设计不一定能方便框架的动态代理选择,不过原理上看二者还是有所联系的。