java动态代理有两种方式,一种是通过jdk自带的接口实现的,另外的一种是通过cglib改字节码实现的。
一、JDK动态代理
jdk动态代理是jdk自带的一个java原生态功能,它是采用java反射包中的Proxy动态生成代理类实现的。动态代理类实现被代理类接口同时将InvocationHandler接口对象注入。
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)做了以下几件事.
(1)根据参数loader和interfaces调用方法 getProxyClass(loader, interfaces)创建代理类$Proxy0.$Proxy0类 实现了interfaces的接口,并继承了Proxy类.
(2)实例化$Proxy0并在构造方法中把DynamicSubject传过去,接着$Proxy0调用父类Proxy的构造器,为h赋值
可以使用java在线反编译: http://javare.cn工具反编译$Proxy0.class。可以发现Proxy0类有接口的实现方法且有h.invoke方法调用。具体见如下示例
从上图第一个红色框动可以看出动态生成的代理类$Proxy0继承Proxy同时实现PersonService;而第二个红色框就是接口的实现方法。
从上述反编译的动态代理类可以清楚知道,jdk动态代理为什么必须有接口了。
调用ProxyGenerator.generateProxyClass生成字节码文件,最后再根据字节码生成Class实例。
1. 为代理类生成一个带参构造器,传入InvocationHandler实例的引用并调用父类的带参构造器
2. 遍历代理方法Map集合,为每个代理方法生成对应的Method类型静态域,并将其添加到fields集合中
3. 遍历代理方法Map集合,为每个代理方法声称对应的MethodInfo对象,并将其添加到methods集合中
4. 为代理类生成静态初始方法,该静态初始方法主要是将每个代理方法的引用赋值给对应的静态字段
代理类默认继承Proxy类,因为Java中只支持单继承,因此JDK动态代理只能实现接口
代理方法都会去调用InvocationHandler的invoke()方法,因此我们需要重写InvocationHandler.invoke()方法
调用invoke()方法时会传入代理实例本身、目标方法和目标方法参数,解释了invoke()方法的参数是怎么来的。
上述部分内容参见:https://www.jianshu.com/p/e10a218d6f35
二、CGLIB动态代理
CGLIB同时ASM框架动态生成代理类字节码,代理类直接继承被代理类并重写他的非final方法。Enhancer可能是CGLIB中最常用的一个类,和Java1.3动态代理中引入的Proxy类差不多(如果对Proxy不懂,可以参考这里)。和Proxy不同的是,Enhancer既能够代理普通的class,也能够代理接口。Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final方法语义决定的。基于同样的道理,Enhancer也不能对fianl类进行代理操作。这也是Hibernate为什么不能持久化final class的原因。
http://javare.cn/1571365081066/de/Hello$$EnhancerByCGLIB$$5a8b589b.java
由于CGLIB的大部分类是直接对Java字节码进行操作,这样生成的类会在Java的永久堆中。如果动态代理操作过多,容易造成永久堆满,触发OutOfMemory异常。
1.JDK动态代理是实现了被代理对象的接口,Cglib是继承了被代理对象。
2.JDK和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。
3.JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。