本系列文章主要是博主在學習spring aop的過程中瞭解到其使用了java動態代理,本著究根問底的態度,於是對java動態代理的本質原理做了一些研究,於是便有了這個系列的文章
為了儘快進入正題,這裡先跳過spring aop和java動態代理的使用流程的講解,這部分內容後面再單獨寫文章整理
不過,我們首先還是先看下java dynamic proxy的基本使用方法,假定我們要代理的物件是一個Map,則程式碼如下:
Map proxyInstance = (Map) Proxy.newProxyInstance( HashMap.class.getClassLoader(), new Class[]{Map.class}, new DynamicInvocationHandler());
之後proxyInstance就可以作為一個正常的Map物件進行使用了
為了對生成物件的屬性做一個基本的瞭解,我們先列印一下proxyInstance的實際型別名稱
System.out.println(proxyInstance.getClass().getName());
得到結果
com.sun.proxy.$Proxy11
如果使用多了,就會發現所有的代理類的名稱都是$Proxy加一個數字,且包名是com.sun.proxy
當我們檢視Proxy.newProxyInstance方法時,會發現它返回的其實是一個Object物件
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
而在實際使用的過程中,它是可以被直接轉型成我們傳入的介面型別,因此可以推測出,該proxyInstance物件的實際型別肯定是實現了我們傳入的介面
我們列印一下該類實現的介面
for (Class intf : proxyInstance.getClass().getInterfaces()) { System.out.println(intf.getName()); }
得到結果
java.util.Map
符合我們之前的推測
接著我們再列印一下該類的父類
System.out.println(proxyInstance.getClass().getSuperclass().getName());
得到結果
java.lang.reflect.Proxy
因此總結一下,該proxyInstance物件有以下3個屬性
1.繼承了Proxy類
2.實現了我們傳入的介面
3.以$Proxy+隨機數字的命名
那麼動態生成代理類的功能究竟是如何實現的呢?接下去就來看java的原始碼
因為原始碼有點多,所以我只貼出關鍵的部分
入口自然是Proxy.newProxyInstance方法
其中有2個部分我們需要關心
第一部分,類的建立
/* * Look up or generate the designated proxy class. */ Class<?> cl = getProxyClass0(loader, intfs);
這個就是實際生成類的方法,後面我們會繼續深究,先略放一放
第二部分,例項的建立
final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; ... return cons.newInstance(new Object[]{h});
最終物件的例項化過程就是通過之前生成的class,獲取其指定引數的建構函式,並將InvocationHandler物件傳入
檢視constructorParams欄位
/** parameter types of a proxy class constructor */ private static final Class<?>[] constructorParams = { InvocationHandler.class };
的確就是獲取InvocationHandler物件的一個建構函式
回想一下之前類定義的第一條,繼承了Proxy類,因此我們去Proxy類中找一下
/** * Constructs a new {@code Proxy} instance from a subclass * (typically, a dynamic proxy class) with the specified value * for its invocation handler. * * @param h the invocation handler for this proxy instance * * @throws NullPointerException if the given invocation handler, {@code h}, * is {@code null}. */ protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; }
在該建構函式中就是將引數h賦值給了成員變數h,這裡名稱h可以記一下,在之後的文章中還會遇到
看完例項的建立,讓我們回到更重要的第一部分,類的生成
進入getProxyClass0(loader, intfs)方法
/** * Generate a proxy class. Must call the checkProxyAccess method * to perform permission checks before calling this. */ 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); }
該方法很簡單,直接從一個cache中拿取物件
檢視proxyClassCache物件
/** * a cache of proxy classes */ private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
該物件本質就是一個類似於Map的快取,不過使用的是WeakCache,這個WeakCache本身的特性我們放到另一篇文章中討論,本文專注於Proxy
我們可以看到該快取的建構函式獲取了2個Factory,顧名思義,第一個是生成key的,第二個是生成ProxyClass的,自然我們需要繼續看第二個Factory
類的註解如下
/** * A factory function that generates, defines and returns the proxy class given * the ClassLoader and array of interfaces. */ private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>
這個就是我們要尋找的負責具體生成類的工廠了,檢視其apply方法
首先其會對傳入的介面型別做一些校驗,包括loader能否載入到傳入的介面,介面是否實際上是介面(因為陣列的型別是Class),介面是否有重複
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<?> intf : interfaces) { /* * Verify that the class loader resolves the name of this * interface to the same Class object. */ Class<?> interfaceClass = null; try { interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } /* * Verify that the Class object actually represents an * interface. */ if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } /* * Verify that this interface is not a duplicate. */ if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } }
接著設定類的預設access_flag,public final
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
接著檢查傳入的介面陣列中是否包含非public的介面,如果有,則生成的類需要和該介面處於同一個package,且訪問屬性會去掉public,只保留final。如果有多個不同package中的非public介面,則報錯
(具體原因大家應該都可以理解)
/* * Record the package of a non-public proxy interface so that the * proxy class will be defined in the same package. Verify that * all non-public proxy interfaces are in the same package. */ for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String name = intf.getName(); int n = name.lastIndexOf('.'); String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { proxyPkg = pkg; } else if (!pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } }
如果沒有非public類,則會使用預設的package名,即com.sun.proxy
if (proxyPkg == null) { // if no non-public proxy interfaces, use com.sun.proxy package proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; }
然後獲取一個靜態自增的int
/* * Choose a name for the proxy class to generate. */ long num = nextUniqueNumber.getAndIncrement();
固定的類名字首
// prefix for all proxy class names private static final String proxyClassNamePrefix = "$Proxy";
將上面三者組合成最終的類名(回想之前我們列印出的例項的類名)
String proxyName = proxyPkg + proxyClassNamePrefix + num;
上面這幾個步驟確定了類的名稱,但還是皮毛,接下去是生成類的血肉:位元組碼
/* * Generate the specified proxy class. */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags);
具體的探究也先放一下,先看位元組碼轉換成具體類的方法
try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { /* * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). */ throw new IllegalArgumentException(e.toString()); }
而該方法是一個native的方法,所以暫時就無法繼續探究了,不過知道了這個方法後,如果我們自己有需要,也可以利用這種機制實現自己的動態類生成,後面會想辦法做一個demo,本文就不做探討了
private static native Class<?> defineClass0(ClassLoader loader, String name, byte[] b, int off, int len);
之前其實都是開胃菜,現在回到之前生成位元組碼的方法,檢視方法原始碼
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) { ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2); final byte[] var4 = var3.generateClassFile(); if (saveGeneratedFiles) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { try { int var1 = var0.lastIndexOf(46); Path var2; if (var1 > 0) { Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar)); Files.createDirectories(var3); var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class"); } else { var2 = Paths.get(var0 + ".class"); } Files.write(var2, var4, new OpenOption[0]); return null; } catch (IOException var4x) { throw new InternalError("I/O exception saving generated file: " + var4x); } } }); } return var4; }
中間if部分的程式碼可以先忽略,不過我們會在後面的文章中使用到這部分功能,這裡先關注下面這2行程式碼
ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2); final byte[] var4 = var3.generateClassFile();
這裡讓我們記一下
var0是類名
var1是介面
var3是access_flag
後面我會盡量將這些varX轉換成更實際的命名,方便大家理解
之後就是本文的最終的重點,也是難點,即二進位制位元組碼的實際生成過程,包括jvm操作指令,所以我們需要先對class檔案的結構和jvm操作指令有一個瞭解,見下篇文章
總結而言:java動態代理的基本原理就是在執行時生成位元組碼,並通過一個native方法將其轉換成Class物件供我們使用