什么是动态代理?
动态代理是在运行期间动态的生成一个代理类,并通过代理类来实现委托类的调用,并添加额外逻辑,动态代理又分为JDK动态代理和CGLib动态代理。
代理模式的目的是为了提供额外或不同的操作,使用代理类替代”实际”对象的进行方法调用,这些操作涉及到与”实际”对象的通信,因此代理通常充当中间人角色。Java的动态代理比代理的思想更前进了一步,它可以动态地创建并代理并动态地处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的策略。
JDK动态代理和CGLib动态代理的区别:
- JDK动态代理是面向接口的,CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,那么抱歉会失败)。
- JDK和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。
- JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。
JDK动态代理使用方法
- 声明一个接口,实现接口
- 实现InvocationHandler接口并重写invoke方法,在其中添加额外逻辑
- 利用Proxy.newProxyInstance方法生成代理类实例
- 利用代理类实例调用方法
//1、声明一个接口,实现接口
public interface Animal {
void eat();
void run();
}
public class Dog implements Animal {
@Override
public void eat() {
System.out.println("i am eating ……");
}
@Override
public void run() {
System.out.println("i am running ……");
}
}
//2、实现InvocationHandler接口并重写invoke方法,在其中添加额外逻辑
public class DynamicProxy implements InvocationHandler {
public DynamicProxy(Animal animal) {
this.animal = animal;
}
Animal animal;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("inter DynamicProxy...");
System.out.println(method);
method.invoke(animal,args);
System.out.println("quite DynamicProxy...");
return null;
}
}
//3、利用Proxy.newProxyInstance方法生成代理类实例
public class DynamicProxyClient {
public static void main(String[] args){
InvocationHandler handler = new DynamicProxy(new Dog());
Animal animal = (Animal) Proxy.newProxyInstance(handler.getClass().getClassLoader(),Dog.class.getInterfaces(),handler);
//4、利用代理类实例调用方法
animal.eat();
animal.run();
}
}
JDK动态代理实现原理
JDK动态代理基于拦截器和反射来实现。
在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),其中每一个动态代理类都必须要实现InvocationHandler这个接口,并重写invoke方法来实现代理类的额外逻辑,而Proxy的作用就是用来动态创建一个代理类的实例,并将代理类实例和handler关联起来,当我们通过代理实例调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。
要明白动态代理的实现原理,最重要的就是弄懂Proxy.newProxyInstance方法,他产生的对象为什么可以转化为Animal对象?方法内部又是怎样将代理实例的方法调用转发到invoke方法上的?弄懂了这两个问题,也就明白了动态代理的实现原理。
首先看newProxyInstance方法,主干代码其实只有3条:
Class<?> cl = getProxyClass0(loader, intfs);
final Constructor<?> cons = cl.getConstructor(constructorParams);
return cons.newInstance(new Object[]{h});
这里可以发现,动态代理实际上就是利用反射获取代理类的实例,并在创建实例时传入handler对象:
- 通过getProxyClass0(loader, intfs)来获取class对象,其中loader是handler的classloader, intfs是被代理类实现的接口class对象数组
- 获取代理类的构造器(通过源码可以发现这个类是否public都可以被创建实例,也就是反射会让访问限制失效)
- 将handler作为参数构造代理类实例对象
那么getProxyClass0(loader, intfs)方法是怎么生成代理类的呢(JDK1.8)?他是怎么实现了指定的借口?怎么将方法的调用转化为调用handler的invoke方法的?起主干代码如下:
return proxyClassCache.get(loader, interfaces);
可以发现getProxyClass0方法是在proxyClassCache中查找loader和interfaces对应的class对象,如果查找为null则新建一个class对象,我们可以找到proxyClassCache的构造方法:
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
其中ProxyClassFactory的apply方法就是生成动态代理类的方法,其中的主干代码是:
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);//返回代理对象二进制class文件
return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);//返回根据class文件加载得到的class对象(本地方法)
我们没有继续看如何生成二进制的字节码和如何将class文件加载为class对象,因为这已经超出了动态范围原理的范畴,接下来我们将代理类的class文件下载下来并反编译看一下结果。反编译步骤如下:
- 设置System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");或者在VMoptions中加入参数-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true可以保存生成的动态代理类的class文件,生成的代理class文件在工程根目录的com\sun\proxy中。
- 反编译class文件:在线反编译工具
得到反编译结果,可以发现代理类就是通过反射获取类对应的Method,并在相应的方法中调用handler的invoke方法,到这里我们就完整的理解了JDK动态代理的实现原理,总结一下就是:我们将委托类(被代理的类)以接口和实现类的方式实现,然后Proxy.newInstance()方法会根据委托类的接口获取相关类信息,并传入handler来关联代理类和委托类,生成新的代理类的字节码文件,最后将字节码文件加载成class对象返回,在调用代理类时,实际上是通过handler调用invoke方法来调用委托类并实现额外逻辑。
package com.sun.proxy;
import com.meituan.data.springbootdemo.Animal;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0
extends Proxy
implements Animal {
private static Method m1;
private static Method m3;
private static Method m4;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler invocationHandler) throws {
super(invocationHandler);
}
public final boolean equals(Object object) throws {
try {
return (Boolean)this.h.invoke(this, m1, new Object[]{object});
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void run() throws {
try {
this.h.invoke(this, m3, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void eat() throws {
try {
this.h.invoke(this, m4, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString() throws {
try {
return (String)this.h.invoke(this, m2, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode() throws {
try {
return (Integer)this.h.invoke(this, m0, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.meituan.data.springbootdemo.Animal").getMethod("run", new Class[0]);
m4 = Class.forName("com.meituan.data.springbootdemo.Animal").getMethod("eat", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
}
catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
}
动态代理使用场景
动态代理的使用场景主要是在类的某些方法前后添加一些操作而不破坏原有类,
- mybatis框架
- Spring的aop
- RPC远程调用