按照网上的惯例,先给出静态代理到动态代理的例子吧;要不然后面的话理论和源码分析没有铺垫,大家都有点迷糊。
静态代理:
/** * 声音接口,用于代理接口 */ public interface IVoice { void song(); }
/** * 歌手类 */ public class Singer implements IVoice{ public void song(){ System.out.println("歌手在唱歌..."); } }
/** * 代理类:喇叭(英语单词太难了,怕你们不认识,直接用拼音了) */ public class LaBa implements IVoice{ private IVoice voice; public LaBa(IVoice voice) { this.voice = voice; } public void song() { System.out.println("歌手的声音被我放大了"); this.voice.song(); System.out.println("歌手停止说话了"); } }
执行类:
public class Main { public static void main(String[] args) { Singer singer=new Singer(); IVoice voice=new LaBa(singer); voice.song(); } }
打印结果:
上面的接口似乎没有什么作用,如果我们把接口去掉呢?
/** * 歌手类 */ public class Singer { public void song(){ System.out.println("歌手在唱歌..."); } }
/** * 代理类:喇叭(英语单词太难了,怕你们不认识,直接用拼音了) */ public class LaBa { private Singer singer; public LaBa(Singer singer) { this.singer = singer; } public void song(){ System.out.println("歌手的声音被我放大了"); this.singer.song(); System.out.println("歌手停止说话了"); } }
public class Main { public static void main(String[] args) { Singer singer=new Singer(); LaBa laba=new LaBa(singer); laba.song(); } }
打印的结果和上面的一模一样,那么没有了接口的代理还算不算代理呢
或者干脆我连代理的方法名字song()都和Singer类中的song()方法名不一样。
这样子:
/** * 歌手类 */ public class Singer { public void song(){ System.out.println("歌手在唱歌..."); } } /** * 代理类:喇叭(英语单词太难了,怕你们不认识,直接用拼音了) */ public class LaBa { private Singer singer; public LaBa(Singer singer) { this.singer = singer; } public void labaSong(){ System.out.println("歌手的声音被我放大了"); this.singer.song(); System.out.println("歌手停止说话了"); } } public class Main { public static void main(String[] args) { Singer singer=new Singer(); LaBa laba=new LaBa(singer); laba.labaSong(); } }
这种没有接口,代理类和实际执行类的方法都不一样了,还算不算代理呢?
找找度娘吧:
定义里面并没有规定使用接口和代理方法名一样才叫代理。所以只要实现了一个对象对另一个对象的访问以及方法调用就是代理模式,现在看来我们在代码中随随便便一个对象方法的调用都是一个模式的使用了。
还是回到大家都认可的有接口的静态代理上来吧;
从那个静态代理的例子里面能看出来,
我们实实在在的创建了一个代理类LaBa.java,并且编辑器编译成class字节码文件,然后被虚拟机导入,进行校验,分析
最终在内存里面创建出一个对象,让他实现了代理功能,供我们使用;
这种模式在被代理的方法确定的情况下,是能满足需求的;反之,则无法使用静态代理模式了。
相对于静态代理就是动态代理了。
动态代理模式就是用程序来创建代理对象的模式。
JDK动态代理模式:
按照惯例先上代码,熟悉基本用法吧。
/** * 声音接口,用于代理接口 */ public interface IVoice { Object song(); }
/** * 歌手类 */ public class Singer implements IVoice{ public Object song(){ System.out.println("歌手在唱歌..."); return null; } }
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; //方法委托类 public class MethodHandler implements InvocationHandler { //原对象 private Object orgObject; public MethodHandler(Object orgObject) { this.orgObject = orgObject; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("歌手的声音被我放大了"); //res是被代理方法的返回值 Object res=method.invoke(orgObject,args); System.out.println("歌手停止说话了"); return res; } }
import java.lang.reflect.Proxy; //执行类 public class Main { public static void main(String[] args) { Singer singer=new Singer(); MethodHandler methodHandler=new MethodHandler(singer); IVoice proxy=(IVoice) Proxy.newProxyInstance(singer.getClass().getClassLoader(), singer.getClass().getInterfaces(), methodHandler); proxy.song(); } }
打印结果:
我们一般是在idea或者eclipse上面开发java项目,编辑器帮我们做好了编译工作或者说帮我们编译成了class文件,然后可以直接执行;
也就是说编辑器帮我们做好了javac(编译成class文件)和java(执行java程序)命令的工作。
那么jdk代理是
首先创建了类,然后自己编译成class文件,载入到虚拟机里面,创建类对象
还是直接创建了class文件,载入到虚拟机里面,创建类对象,
还是有利用黑科技直接跳过虚拟机的检测和分析,直接在虚拟机里面搞出来一个对象。
这个我们要从源码去入手了。
首先来看 Proxy.newProxyInstance ()这个方法
@CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h) throws IllegalArgumentException { 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. */ 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() { 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); } }
删除多余的代码,并添加一些注释
public static Object newProxyInstance(ClassLoader loader,Class>[] interfaces,InvocationHandler h) throws IllegalArgumentException{ final Class>[] intfs = interfaces.clone(); //获得代理类对象 Class> cl = getProxyClass0(loader, intfs); //代理类对象的构造器函数 final Constructor> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; //实例化一个对象 return cons.newInstance(new Object[]{h}); } }
继续查看 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 //缓存里面有就直接取,没有的话就通过ProxyClassFactory类创建 return proxyClassCache.get(loader, interfaces); }
这里直接从Cache里面取出来的,有get就一定有添加的方法;在看一下上面的注释;我们目前不知道ProxClassFactory类在哪里,也不知道是怎么创建的。
还是老老实实的追到 proxyClassCache.get(loader, interfaces);方法里面去;
1 public V get(K key, P parameter) { 2 Objects.requireNonNull(parameter); 3 4 expungeStaleEntries(); 5 6 Object cacheKey = CacheKey.valueOf(key, refQueue); 7 8 // lazily install the 2nd level valuesMap for the particular cacheKey 9 ConcurrentMap
代码比较长,先从30行return处开始向上分析,去除多余代码,代码如下:
1 public V get(K key, P parameter) { 2 Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); 3 Suppliersupplier = valuesMap.get(subKey); 4 Factory factory = null; 5 6 while (true) { 7 if (supplier != null) { 8 V value = supplier.get(); 9 if (value != null) { 10 return value; 11 } 12 } 13 } 14 }
value来自 supplier ; supplier 来自valuesMap.get(subKey);那么由此推断subKey是代理类对象的key值,
那么 subKeyFactory.apply(key, parameter)就一定是创建代理类对象的方法,并且添加到缓存中去的。
查看 subKeyFactory.apply(key, parameter)源码:
找到了之前提到了ProxyClassFactory类。确认无疑了,就是在这里创建的代理类对象
apply()源码
源码经过删减,并且添加了中文注释。
1 public Class> apply(ClassLoader loader, Class>[] interfaces) { 2 Map, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); 3 //我们目前只有一个接口,所以不用考虑循环问题 4 for (Class> intf : interfaces) { 5 6 Class> interfaceClass = null; 7 try { 8 //获取接口类对象 9 interfaceClass = Class.forName(intf.getName(), false, loader); 10 } 11 /* 12 * Verify that the Class object actually represents an 13 * interface. 14 判断传过来的类对象参数是否是接口 15 */ 16 if (!interfaceClass.isInterface()) { 17 throw new IllegalArgumentException( 18 interfaceClass.getName() + " is not an interface"); 19 } 20 } 21 22 String proxyPkg = null; // package to define proxy class in 23 int accessFlags = Modifier.PUBLIC | Modifier.FINAL; 24 25 26 27 if (proxyPkg == null) { 28 // if no non-public proxy interfaces, use com.sun.proxy package 29 //如果为空,使用com.sun.proxy package作为代理对象的包名 30 proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; 31 } 32 33 /* 34 * Choose a name for the proxy class to generate. 35 nextUniqueNumber是一个AtomicLong值,线程安全的原子操作,先获取当前值,在把内部的long值+1 36 */ 37 long num = nextUniqueNumber.getAndIncrement(); 38 // private static final String proxyClassNamePrefix = "$Proxy"; 39 //最终路径大概这样:com.sun.proxy package.$Proxy0 40 String proxyName = proxyPkg + proxyClassNamePrefix + num; 41 42 /* 43 * Generate the specified proxy class. 44 创建一个指定的代理类 45 */ 46 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( 47 proxyName, interfaces, accessFlags); 48 try { 49 return defineClass0(loader, proxyName, 50 proxyClassFile, 0, proxyClassFile.length); 51 } 52 } 53 }
大概可以推断出46行生成了字节数组,defineClass0()将字节数组实例化成了一个对象并返回上级方法,然后返回给我们;这里先把defineClass0()放一放,先看看
ProxyGenerator.generateProxyClass()是如何产生字节数组的。
追进去看看。
1 public static byte[] generateProxyClass(final String var0, Class>[] var1, int var2) { 2 ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2); 3 final byte[] var4 = var3.generateClassFile(); 4 //private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles")); 5 //保存产生的文件 6 if (saveGeneratedFiles) { 7 AccessController.doPrivileged(new PrivilegedAction() { 8 public Void run() { 9 try { 10 int var1 = var0.lastIndexOf(46); 11 Path var2; 12 if (var1 > 0) { 13 Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar)); 14 Files.createDirectories(var3); 15 var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class"); 16 } else { 17 var2 = Paths.get(var0 + ".class"); 18 } 19 20 Files.write(var2, var4, new OpenOption[0]); 21 return null; 22 } catch (IOException var4x) { 23 throw new InternalError("I/O exception saving generated file: " + var4x); 24 } 25 } 26 }); 27 } 28 29 return var4; 30 }
从27行 return往分析;第六行 saveGeneratedFiles 判断是否保存生成的文件,可以推断出var4就是最终完整的字节数组。
这个地方记住第4行中的路径 sun.misc.ProxyGenerator.saveGeneratedFiles。后面我们需要用这个路径把字节码文件输出到本地来。
既然是第三行生成的字节数组,那么我们就追进去看看内容吧
generateClassFile()
删除了部分代码,可以大概看到var14向var13里面写了很多字节数据;
private byte[] generateClassFile() { //添加hashCode方法 this.addProxyMethod(hashCodeMethod, Object.class); //添加equals方法 this.addProxyMethod(equalsMethod, Object.class); //添加toString方法 this.addProxyMethod(toStringMethod, Object.class); Class[] var1 = this.interfaces; int var2 = var1.length; //方法和字段都不能超过65535个,不过我们在自定义类的时候一般不会超过这个数 if (this.methods.size() > 65535) { throw new IllegalArgumentException("method limit exceeded"); } else if (this.fields.size() > 65535) { throw new IllegalArgumentException("field limit exceeded"); } else { ByteArrayOutputStream var13 = new ByteArrayOutputStream(); DataOutputStream var14 = new DataOutputStream(var13); try { var14.writeInt(-889275714); var14.writeShort(0); var14.writeShort(49); this.cp.write(var14); var14.writeShort(this.accessFlags); var14.writeShort(this.cp.getClass(dotToSlash(this.className))); var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy")); var14.writeShort(this.interfaces.length); Class[] var17 = this.interfaces; int var18 = var17.length; for(int var19 = 0; var19 < var18; ++var19) { Class var22 = var17[var19]; var14.writeShort(this.cp.getClass(dotToSlash(var22.getName()))); } var14.writeShort(this.fields.size()); var15 = this.fields.iterator(); while(var15.hasNext()) { ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next(); var20.write(var14); } var14.writeShort(this.methods.size()); var15 = this.methods.iterator(); while(var15.hasNext()) { ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next(); var21.write(var14); } var14.writeShort(0); return var13.toByteArray(); } } }
现在回到defineClass0()方法。
这是一个native方法,直接调用虚拟机的方法了;完了,看不到怎么实现的了。我猜测defineClass0()调用的是类加载器里面的方法。
如果对类加载器不熟悉的,可以看看这篇文章:java 自定义类加载器 ,你会发现我们自定义的类加载器也是将class文件读取成字节数组交给虚拟机的。
总结:
JDK动态代理的代理对象来自一个字节数组;
这个数组存放的是已经编译过后的内容;
并且将字节数组交给虚拟机进行实例化。
上面过道可以将字节码写入到本地文件中。现在来实现这个功能。
//执行类 public class Main { public static void main(String[] args) { System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); Singer singer=new Singer(); MethodHandler methodHandler=new MethodHandler(singer); IVoice proxy=(IVoice) Proxy.newProxyInstance(singer.getClass().getClassLoader(), singer.getClass().getInterfaces(), methodHandler); proxy.song(); } }
我这边使用idea运行的,到项目的根目录下看看。
和我们平时看到的class文件差不多,反正也不怎么能看明白。
利用jd-gui对代码进行反编译:
package com.sun.proxy; import f.d.IVoice; 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 IVoice { private static Method m1; private static Method m2; private static Method m3; private static Method m0; public $Proxy0(InvocationHandler paramInvocationHandler) { super(paramInvocationHandler); } public final boolean equals(Object paramObject) { try { return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); } catch (Error|RuntimeException error) { throw null; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final String toString() { try { return (String)this.h.invoke(this, m2, null); } catch (Error|RuntimeException error) { throw null; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final Object song() { try { return this.h.invoke(this, m3, null); } catch (Error|RuntimeException error) { throw null; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final int hashCode() { try { return ((Integer)this.h.invoke(this, m0, null)).intValue(); } catch (Error|RuntimeException error) { throw null; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m3 = Class.forName("f.d.IVoice").getMethod("song", 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()); } } }
JDK动态代理就说到这了,后面继续说CGLIB动态代理。