之前介绍的反射和注解都是Java中的动态特性,还有即将介绍的动态代理也是Java中的一个动态特性。这些动态特性使得我们的程序很灵活。动态代理是面向AOP编程的基础。通过动态代理,我们可以在运行时动态创建一个类,实现某些接口中的方法,目前为止该特性已被广泛应用于各种框架和类库中,例如:Spring,Hibernate,MyBatis等。理解动态代理是理解框架底层的基础。
主要内容如下:
- 理解代理是何意
- Java SDK实现动态代理
- 第三方库cglib实现动态代理
一、代理的概念
单从字面上理解,代理就是指原对象的委托人,它不是原对象但是却有原对象的权限。Java中的代理意思类似,就是指通过代理来操作原对象的方法和属性,而原对象不直接出现。这样做有几点好处:
- 节省创建原对象的高开销,创建一个代理并不会立马创建一个实际原对象,而是保存一个原对象的地址,按需加载
- 执行权限检查,保护原对象
实际上代理堵在了原对象的前面,在代理的内部往往还是调用了原对象的方法,只是它还做了其他的一些操作。下面看第一种实现动态代理的方式。
二、Java SDK实现动态代理
实现动态代理主要有如下几个步骤:
- 实现 InvocationHandler接口,完成自定义调用处理器
- 通过Proxy的getProxyClass方法获取对应的代理类
- 利用反射技术获取该代理类的constructor构造器
- 利用constructor构造代理实例对象
在一步步解析源码之前,我们先通过一个完整的实例了解下,整个程序的一步步逻辑走向。
//定义了一个调用处理器
public class MyInvotion implements InvocationHandler {
private Object realObj;
public MyInvotion(Object obj){
this.realObj = obj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
//通过代理执行原对象的方法
return method.invoke(realObj,args);
}
}
//定义一个接口
public interface MyInterface {
public void sayHello();
}
//该接口的一个实现类,该类就是我们的原对象
public class ClassA implements MyInterface {
public void sayHello(){
System.out.println("hello walker");
}
}
//main 函数
public static void main(String[] args){
ClassA a = new ClassA();
MyInvotion myInvotion = new MyInvotion(a);
Class myProxy = Proxy.getProxyClass(ClassA.class.getClassLoader(), new Class[]{MyInterface.class});
Constructor constructor =myProxy.getConstructor(new Class[]{InvocationHandler.class});
MyInterface m = (MyInterface)constructor.newInstance(myInvotion);
m.sayHello();
}
输出结果:hello walker
简单说下整体的运行过程,首先我们创建ClassA 实例并将它传入自定义的调用处理器MyInvotion,在MyInvotion中用realObj接受该参数代表原对象。接着调用Proxy的getProxyClass方法,将ClassA 的类加载器和ClassA 的实现的接口集合传入,该方法内部会实现所有接口返回该类的代理类,然后我们利用反射获取代理类的构造器并创建实例。
以上便是整个程序运行的大致流程,接下来我们从源代码的角度看看具体是如何实现的。首先我们看InvocationHandler接口,这是我们的调用处理器,在代理类中访问的所有的方法都会被转发到这执行,具体的等我们看了代理类源码及理解了。该接口中唯一的方法是:
public Object invoke(Object proxy, Method method, Object[] args)
- 参数Proxy表示动态生成的代理类的对象,基本没啥用
- 参数method表示当前正在被调用的方法
- 数组args指定了该方法的参数集合
我们上例中对该接口的实现情况,定义了一个realObj用于保存原对象的引用。重写的invoke方法中调用了原对象realObj的method方法,具体谁来调用该方法以及传入的参数是什么,在看完代理类源码即可知晓。
接下来我们看看最核心的内容,如何动态创建代理类。这是getProxyClass方法的源码:
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
{
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
return getProxyClass0(loader, intfs);
}
首先获取了该类实现的所有的接口的集合,然后判断创建该代理是否具有安全性问题,检查接口类对象是否对类装载器可见等。然后调用另外一个getProxyClass0方法,我们跟进去:
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
判断如果该类的接口超过65535(想必还没有那么牛的类),抛出异常。在我们的Proxy类中有个属性proxyClassCache,这是一个WeakCache类型的静态变量。它指示了我们的类加载器和代理类之间的映射。所以proxyClassCache的get方法用于根据类加载器来获取Proxy类,如果已经存在则直接从cache中返回,如果没有则创建一个映射并更新cache表。具体创建一个Proxy类并存入cache表中的代码限于能力,未能参透。
至此我们就获取到了该ClassA类对应的代理类型,接着我们通过该类的getConstructor方法获取该代理类的构造器,并传入InvocationHandler.class作为参数,至于为何要传入该类型作为参数,等会看代理类源码变一目了然了。
最后newInstance创建该代理类的实例,实现对ClassA对象的代理。
可能看完上述的介绍,你还会有点晕。下面我们通过查看动态生成的代理类的源码来加深理解。上述getProxyClass方法会动态创建一个代理类并返回他的Class类型,这个代理类一般被命名为$ProxyN,这个N是递增的用于标记不同的代理类。我们可以利用反编译工具反编译该class:
final class $Proxy0 extends Proxy implements MyInterface {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject) {
return ((Boolean) this.h.invoke(this, m1,
new Object[] { paramObject })).booleanValue();
}
public final void sayHello() {
this.h.invoke(this, m3, null);
}
public final String toString() {
return (String) this.h.invoke(this, m2, null);
}
public final int hashCode() {
return ((Integer) this.h.invoke(this, m0, null)).intValue();
}
static {
m1 = Class.forName("java.lang.Object").getMethod("equals",
new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("laoma.demo.proxy.SimpleJDKDynamicProxyDemo$IService")
.getMethod("sayHello",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]);
}
}
这就是上述的ClassA的动态代理类,我们看到该类的构造方法传入参数InvocationHandler类型,并调用了父类Proxy的构造方法保存了这个InvocationHandler实例,这也解释了我们为什么在获取构造器的时候需要指定参数类型为InvocationHandler,就是因为动态代理类只有一个构造器并且参数类型为InvocationHandler。
接着我们看其中的方法,貌似只有一个sayHello是我们知道的,别的方法哪来的?我们说过在动态创建代理类的时候,会实现原对象的所有接口。所以sayHello方法是实现的MyInterface。而其余的四个方法是代理类由于比较常用,被默认添加到其中。而这些方法的内部都是调用的this.h.invoke这个方法,this.h就是保存在父类Proxy中的InvocationHandler实例(我们用构造器向其中保存的),调用了这个类的invoke方法,在我们自定义的InvocationHandler实例中重写了invoke方法,我们写的比较简单,直接执行传入的method。
也就是我们调用代理类的任何一个方法都会转发到该InvocationHandler实例中的involve中,因为该实例中保存有我们的原对象,所以我们可以选择直接调取原对象中的方法作为回调。
以上便是有关Java SDK中动态代理的相关内容,稍微总结下,首先我们通过实现InvocationHandler自定义一个调用处理类,该类中会保存我们的原对象,并提供一个invoke方法供代理类使用。然后我们通过getProxyClass方法动态创建代理类,最后用反射获取代理类的实例对象。
需要注意的是:以上我们使用的四步创建代理实例时最根本的,其实Proxy中提供一个方法可以封装2到4步的操作。上述代码也可以这么写:
ClassA a = new ClassA();
MyInterface aProxy = (MyInterface)Proxy.newProxyInstance(ClassA.class.getClassLoader(),new Class<?>[]{MyInterface.class},new MyInvotion(a));
aProxy.sayHello();
我们打开该方法的内部源码,其实走的还是我们上述的过程,它就是做了封装。
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
{
Objects.requireNonNull(h);
//获取所有接口
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
//创建动态代理类
Class<?> cl = getProxyClass0(loader, intfs);
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
.............
..............
}
二、第三方库cglib实现动态代理
使用动态代理,我们编写通用的代码逻辑,即仅实现一个InvocationHandler实例完成对多个类型的代理。但是我们从动态生成的代理类的源码可以看到,所有的代理类都继承自Proxy这个类,这就导致我们这种方式不能代理类,只能代理接口。因为java中是单继承的。也就是说,给我们一个类型,我们只能动态实现该类所有的接口类型,但是该类继承的别的类我们在代理类中是不能使用的,因为它没有被代理类继承。下面看个例子:
public class ClassB {
public void welcome(){
System.out.println("welcom walker");
}
}
public interface MyInterface {
public void sayHello();
}
//需要被代理的原类型,继承了ClassB和接口MyInterface
public class ClassA extends ClassB implements MyInterface {
public void sayHello(){
System.out.println("hello walker");
}
}
//InvocationHandler 实例
public class MyInvotion implements InvocationHandler {
private Object realObj;
public MyInvotion(Object obj){
this.realObj = obj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
return method.invoke(realObj,args);
}
}
我们反编译该代理类,和上述的源码是一样的,此处不再重复贴出。我们能从中看出来的是,我们的代理只会实现原类型中所有的接口,至于原类型所继承的类,在生成Proxy代理类的时候会丢弃,因为所有的代理类必须继承Proxy类,这就导致原类型的父类中的方法 在代理类中丢失。这是该种方式的一大弊端。下面我们看看另一种方式实现动态代理,该种方式完美解决了这种不足。
限于篇幅,我们下篇介绍cglib实现动态代理机制的内容,本篇暂时结束,总结的不好,望海涵。