动态代理是什么
首先说下代理模式,代理模式是常见的一种java设计模式,特征是代理类与委托类实现了同样的接口,代理类主要负责为委托类预处理、过滤、转发,以及事后处理等。代理类与委托类之间通常会存在关联关系,一个代理类的实例与它的委托类的实例是关联的。代理类的实例本身是并不真正关心被调用方法的内部逻辑,而是会通过内部访问调用 委托类的实例真正实现了的方法,来为调用者提供服务。
有代理的话,在访问实际对象时,是通过代理实例来访问、调用委托类方法的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。
动态代理对比静态代理,最大的特点是代理类是在程序运行时生成的,并非在编译期生成,能做的事情也多了,自然风险也高了。
动态代理最简单的用法
用一个比较接近生活的例子:中午,饿了的室友 委托 持家有道的你 去点外卖
Hungry.java :接口
public interface Hungry { void callLunch(); }
Roommate.java :Hungry接口的实现类,也就是委托类
public class Roommate implements Hungry{ private String name; public Roommate(String name) { this.name = name; } @Override public void callLunch() { System.out.println("好饿,今天午饭点外卖吧"); } }
public class RoommateInvocationHandler<T> implements InvocationHandler { private T rommate; public RoommateInvocationHandler(T roommate){ this.rommate = roommate; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("下单前,我先帮你看下有没有平台优惠券吧"); Object result = method.invoke(rommate , args); return result; } }
InvocationHandler是一个接口,由代理实例内部的invocation handler实现的接口。每个代理实例都有一个关联的invocation handler。当代理实例上调用方法时,method.invoke(baseImpl, args),此方法将被编码并织入到代理实例内部的 invocation handler实现的invoke方法中。
利用 Proxy 的方式实现动态代理,调用 委托类接口 的方法,完成午餐点外卖这个操作
public static void main(String[] args) { Roommate roommate = new Roommate("zhangsan"); Hungry proxyInstance = (Hungry) Proxy.newProxyInstance( roommate.getClass().getClassLoader(), roommate.getClass().getInterfaces(), new RoommateInvocationHandler<Roommate>(roommate) ); proxyInstance.callLunch(); }//输出结果下单前,我先帮你看下有没有平台优惠券吧 好饿,今天午饭点外卖吧
代理实例proxyInstance的类型是Hungry,所以只能调用Hungry里规定的方法。Roommate作为接口实现类,不是来自接口的其他的方法,是无法通过动态代理调用的。
可以看到代理实例在调用委托类实现的方法时,可以很方便地在调用方法的前后执行一些操作,在示例代码中则是在调用方法前简单输出了一行: System.out.println("下单前,我先帮你看下有没有平台优惠券吧"),还可以有其他用途,例如记录这个方法的耗时时间,对方法的参数或者返回结果进行修改等等。这也是Spring,Dagger进行AOP编程的原理。
那为什么继承InvocationHandler接口和持有委托类引用的RoommateInvocationHandler调用来自Hungry接口的callLunch()方法时可以调用到委托类对callLunch()的逻辑实现呢,看看它的背后原理:
动态代理的实现原理
从Proxy.newProxyInstance() 入手,逐步分析 InvocationHandler 如何建立代理实例和委托实例的关联:
public static Object newProxyInstance(ClassLoader loader , Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { //InvocationHandler必须非空,说明是个重要角色 Objects.requireNonNull(h); //获取委托类的接口 final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. * 核心:通过类加载器和委托类接口,在内存中查找出或者生成指定的代理类 */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. * 利用指定的invocation handler调用它的构造器方法,构建代理类的实例返回 */ 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}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }
来到这一步好像就停下了,那么接下来探究 cl 这个实例创建过程发生了什么:
在上面示例代码main函数的后面接着补充。利用ProxyGenerator.generateProxyClass生成这个动态生成的类文件,写入了指定路径的class文件内
$Proxy0 是 代理类在系统内部的编号,在示例代码只生成了一个代理类所以编号是 $Proxy0 。
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",Roommate.class.getInterfaces()); String filePath = "C:\\Users\\ODM\\Desktop\\RoommateProxy.class"; try(FileOutputStream fos = new FileOutputStream(filePath)) { fos.write(classFile); fos.flush(); }catch (IOException e){ e.printStackTrace(); System.out.println("error:写入文件"); }
使用反编译工具,我这里用的是jd-gui反编译,这个$Proxy0类,实现了Proxy类,继承了和委托类相同的接口
import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;import proxy_test.Hungry;public final class $Proxy0 extends Proxy implements Hungry{ private static Method m1; private static Method m3; //由下方静态代码块得知,m3代表callLunch()这一个方法 private static Method m2; private static Method m0; /* * 父类Proxy的构造器,其中 h 属性为 InvocationHandler引用 * protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; } */ public $Proxy0(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } //关键!可供外界调用,方法名与委托类实现接口的方法相同,利用 InvocationHandler调用invoke public final void callLunch() throws { try{ this.h.invoke(this, m3, null); return; } catch (Error|RuntimeException localError){ throw localError; } catch (Throwable localThrowable){ throw new UndeclaredThrowableException(localThrowable); } } public final boolean equals(Object paramObject) throws {} public final String toString() throws {...} public final int hashCode() throws {...} static{ try{ m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m3 = Class.forName("proxy_test.Hungry").getMethod("callLunch", 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 localNoSuchMethodException){ throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException){ throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } }
事情逐渐明朗起来,从这个动态类的源码,可以分析出:$Proxy0 ,在构建这个类时,会调用了父类Proxy的构造方法,将InvocationHandler引用传递给了父类Proxy的 h 属性,于是当我们在外界使用 代理实例 调用了 callLunch() 这个方法时,就会来到这一句 this.h.invoke(this, m3, null); 由于h属性其实是InvocationHandler引用,调用了它的invoke,也就导致了上面示例代码中的RoommateInvocationHandler类的重写过的invoke方法也就被调用了,RoommateInvocationHandler也持有委托类的引用,所以委托类的方法也被调用起来了。
Java的继承机制是单继承,多接口。代理类因为必须要继承Proxy类,所以java的动态代理只能对接口进行代理,无法对一个class类进行动态代理。
动态代理原理总结
用大白话的方式讲:
有一个类InvocationHandler,它的性质类似一个中介,中介类构建时持有了委托对象,所以可以在它的invoke方法中调用了委托对象实现接口的具体方法。当外部调用这个InvocationHandler的invoke方法时,对 invoke 的调用最终都转为对委托对象的方法调用。
创建明面上负责代理的代理实例时,在内存中动态生成的类不但继承了Proxy,也实现了与委托对象相同的接口,因此代理实例可以调用此接口的方法,然后通过持有的中介类对象来调用中介类对象的invoke方法,最终达到代理实例执行了委托者的方法。
发表评论 取消回复