JDK動態代理是代理模式的一種,且只能代理介面。spring也有動態代理,稱為CGLib,現在主要來看一下JDK動態代理是如何實現的?
一、介紹
JDK動態代理是有JDK提供的工具類Proxy實現的,動態代理類是在執行時生成指定介面的代理類,每個代理例項(實現需要代理的介面)都有一個關聯的呼叫處理程式物件,此物件實現了InvocationHandler,最終的業務邏輯是在InvocationHandler實現類的invoke方法上。
也即是在invoke方法上可以實現原方法中沒有的業務邏輯,相當於spring aop的@Before、@After等註解。
二、樣例
(1)介面
public interface ProxySource { void test(); }
(2)實現類
public class ProxySourceImpl implements ProxySource { @Override public void test() { System.out.println("原有業務邏輯"); } }
(3)實現InvocationHandler介面和invoke方法
static class MyHandler implements InvocationHandler { //需要代理的類 Object target; public MyHandler(Object target) { this.target = target; } /** * @param proxy 動態代理例項 * @param method 需要執行的方法 * @param args 方法中引數 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("do other things befor"); method.invoke(target, args); System.out.println("do other things after"); return null; } }
(4)利用Proxy實現代理類
//此引數設定是為了儲存生成代理類的位元組碼檔案 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); ProxySource proxySource = (ProxySource) Proxy.newProxyInstance( ProxySourceImpl.class.getClassLoader(), ProxySourceImpl.class.getInterfaces(), new MyHandler(new ProxySourceImpl()) ); //執行方法 proxySource.test();
(5)方法呼叫結果
可以看到,在原有方法執行前後都執行了其他程式碼。
三、原始碼分析(主要看Proxy.newProxyInstance方法,省略非核心程式碼)
(1)newProxyInstance方法
Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) { final Class<?>[] intfs = interfaces.clone(); //獲取代理介面class Class<?> cl = getProxyClass0(loader, intfs); //獲取到class之後用反射獲取構造方法,然後建立代理類例項 try { final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; return cons.newInstance(new Object[]{h}); } catch (Exception e) { throw new InternalError(e.toString(), e); } }
由上述程式碼可知,主要是通過getProxyClass0方法獲取到代理介面的class
(2)getProxyClass0方法
Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { //如果已經有相應的位元組碼檔案,則之間返回,否則通過代理類工廠建立代理類 return proxyClassCache.get(loader, interfaces); }
而proxyClassCache又是什麼東東呢?
WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
proxyClassCache是一個WeakCache物件,可知這是一個快取物件,這個類結構是通過ConcurrentHashMap實現的,
ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>();
資料結構是(key,sub-key)->value
存的值也就是<ClassLoader,<interfaces,$Proxy.class>>
(3)get方法
public V get(K key, P parameter) { Object cacheKey = CacheKey.valueOf(key, refQueue); //根據classloader為key檢視快取中是否已有 ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey); if (valuesMap == null) { ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>()); if (oldValuesMap != null) { valuesMap = oldValuesMap; } } //獲取到weakcache種的sub-key Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); //根據sub-key去當前類載入器下是否有該代理介面的位元組碼 Supplier<V> supplier = valuesMap.get(subKey); Factory factory = null; while (true) { if (supplier != null) { //supplier是代理類工廠例項 V value = supplier.get(); if (value != null) { return value; } } //建立代理類工廠 if (factory == null) { factory = new Factory(key, parameter, subKey, valuesMap); } //將上述建立的代理類工廠直接賦值給supplier if (supplier == null) { supplier = valuesMap.putIfAbsent(subKey, factory); if (supplier == null) { supplier = factory; } } else { if (valuesMap.replace(subKey, supplier, factory)) { supplier = factory; } else { supplier = valuesMap.get(subKey); } } } }
這個supplier.get方法點進去,核心就是ProxyClassFactory的apply方法
(4)ProxyClassFactory的apply方法
Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { for (Class<?> intf : interfaces) { Class<?> interfaceClass = null; try { //通過類許可權定名反射獲取class interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } //判斷是否可以通過系統載入器載入 if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } //校驗是否是介面 if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } } long num = nextUniqueNumber.getAndIncrement(); //生成代理類的許可權定名,例如com.sun.proxy.$Proxy0 String proxyName = proxyPkg + proxyClassNamePrefix + num; //生成位元組碼檔案 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { //呼叫本地方法生成class return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } }
上述程式碼中核心是生成位元組碼,即是ProxyGenerator.generateProxyClass方法。
(5)ProxyGenerator.generateProxyClass方法。
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) { //儲存生成代理類的位元組碼檔案 } return var4; }
上述程式碼中saveGeneratedFiles點進去是這樣的,也即是開頭樣例中的設定屬性。
private static final boolean saveGeneratedFiles = ((Boolean)AccessController .doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();
但主要還是ProxyGenerator的generateClassFile這個方法。
其預設給代理類生成了hashcode、equals和toString方法,也限制了代理介面和欄位都不能超過65535個。
現在來看一下儲存的代理類位元組碼檔案是怎麼樣的(通過idea反編譯後)
public final class $Proxy0 extends Proxy implements ProxySource { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { //程式碼省略 } public final void test() throws { //h是invocationhandler,所以最後是執行invoke方法 super.h.invoke(this, m3, (Object[])null); } public final String toString() throws { //程式碼省略 } public final int hashCode() throws { //程式碼省略 } static { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("com.liusy.lang.ProxySource").getMethod("test"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } }
可以看到,生成的$Proxy0繼承了Proxy,實現了我定義的介面ProxySource,裡面有四個方法,m0~m3,通過靜態程式碼塊中根據類的全限定名和方法名反射獲取,而最後是執行InvocationHandler的invoke方法。
至此,JDK動態代理已經說完,希望對你有所幫助。
=======================================================
我是Liusy,一個喜歡健身的程式設計師。
歡迎關注微信公眾號【Liusy01】,一起交流Java技術及健身,獲取更多幹貨。