详解Java动态代理机制

xiaoxiao2021-02-28  72

     之前介绍的反射和注解都是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实现动态代理机制的内容,本篇暂时结束,总结的不好,望海涵。

转载请注明原文地址: https://www.6miu.com/read-71086.html

最新回复(0)