您好,登錄后才能下訂單哦!
JDK動態代理的原理是什么,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
java對設計模式--代理模式的實現,只能針對接口進行代理。代理模式:提供一個代理對象來持有目標對象的引用,通過對代理對象的操作可以達到操作目標對象的目的。使用代理模式主要是使用者不想或者不能直接操作目標對象,需要一個代理的中間對象來維持聯系。例如Mybatis中Mapper接口并沒有實現類,因此使用者不能直接操作實現類,所以會產生一個代理Mapper。又例如Spring AOP中的Bean,使用者想對Bean的使用進行增強或者其他處理,于是Spring需要返回一個的代理Bean來完成目的。
一接口:
public interface ITodo { void doString(String desc); }
一實現:
public class Todo implements ITodo { @Override public void doString(String desc) { System.out.println("doString: " + desc); } }
目標,對接口的原有方法進行增強。實現方式:JDK動態代理
一代理工具類:
public class ProxyInstance implements InvocationHandler { // 代理目標,即被代理類 private Object target; // 代理類持有被代理類 public ProxyInstance(Object target) { this.target = target; } public <T> T getProxy(){ // 獲取實例方式,這里使用newProxyInstance return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this); } // 這里為代理的處理流程 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("proxy before"); Object obj=method.invoke(target,args); System.out.println("proxy after"); return obj; } }
public class Test { public static void main(String[] args) { // 開啟保存代理中生成文件的代碼 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); ITodo todo = new ProxyInstance(new Todo()).getProxy(); todo.doString("------ nothing-------- "); } }
proxy before doString: ------ nothing-------- proxy after
通過控制臺就可以看到接口原有的方法被增強了,方法執行之前和執行之后執行其他代碼。這就是JDK的動態代理功能。JDK動態代理的使用組件:一接口、一實現、一代理工具類,并且需要遵循以下規則:
作為參數傳入
代理類需要使用Proxy的構造方法獲取實例
一般使用newProxyInstance
定義代理處理邏輯
用于通過反射生成代理類的方法
因此只能代理接口中已定義的方法
接口的所有方法都會被重寫為final類型
用于加載新生成的代理類$Proxy{n}
參數1: 類加載器
參數2: 接口Class數組
參數3: InvocationHandler接口實現類
構造參數為被代理類實現的接口
代理類調用的對象是Proxy類產生的實例,與被代理類不是一個對象
每次調用都需要通過反射來調用
public Object invoke(Object proxy, Method method, Object[] args)
proxy為JDK生成的代理對象$Proxy{n}對象,因此是動態的,只存在于內存中
Method為JDK例如反射獲取的調用方法
args為調用方法的參數
因為該類是生成的,所有需要類加載器從新加載
對上面的示例,查看其生成的代理類源碼如下
package com.sun.proxy; import cn.tinyice.demo.proxy.jdk.ITodo; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; // 代理對象集成java.lang.reflect.Proxy并實現了被代理對象的父接口 // 這里調整了方法位置,便于分析 public final class $Proxy0 extends Proxy implements ITodo { private static Method m1; private static Method m2; private static Method m3; private static Method m0; static { // 需要對接口的equals、toString、hashCode和 目標方法進行重寫,因此先獲取原方法的運行時表示java.lang.reflect.Method try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("cn.tinyice.demo.proxy.jdk.ITodo").getMethod("doString", Class.forName("java.lang.String")); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } // 構造器入參:InvocationHandler實例即代理類實例 public $Proxy0(InvocationHandler var1) throws { // 判斷InvocationHandler不為空,然后賦給變量h super(var1); } // ----------------------------------------- 代理主要關注點: 目標方法 重寫 ------------------------ public final void doString(String var1) throws { try { // super.h=InvocationHandler實例,調用其invoke方法,該方法為用戶重寫的方法 // 三個參數來源確定: proxy=this,method = target的method,args=參數的Object數組(發生類型轉換) super.h.invoke(this, m3, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } // ----- equals、toString、hashCode 重寫,實質調用 InvocationHandler的對應方法------- public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } }
源碼部分主要分為四部分
新生成的代理類定義為:public final class $Proxy0 extends Proxy implements ITodo {...}
,就是重新生成一個新的接口實現類,對原有實現類進行功能復制、增強。
equals、hashCode、toString
這三個方法是Object方法,主要是驗證Java對象的唯一性,與原來的實現類已經不是一個內存地址了以及其他操作。
doString
是原有實現類的方法
InvocationHandler就是使用者編寫的代碼,這一步就是切入
所有被重寫的方法都變為了final
類型。所有的方法都調用了InvocationHandler的invoke
方法。因此這個invoke
方法就是增強的核心方法。
了解以上內容基本可以知道JDK動態代理的底層原理了。一句話:重寫方法調用自定義的invoke
來實現增強
調用入口:獲取代理類
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { ... }
核心代嗎
Class<?> cl = getProxyClass0(loader, intfs);
↓
proxyClassCache.get(loader, interfaces)
proxyClassCache在Proxy類中靜態定義
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
WeakCache實例,一個弱引用緩存對象。存儲結構如下,是 K,P,V三個對象存儲
ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>();
該對象需要2個工廠類,一個用于生成subKey,一個用于生成value
KeyFactory、ProxyClassFactory 均為BiFunction<ClassLoader, Class<?>[], Object>,具有apply方法
獲取時和常規緩存使用方式一致,先通過key從緩存Map中獲取,獲取不到就去生成
ConcurrentMap<Object, Supplier<V>
。首次使用必是生成。
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); // 這里生成subKey用于緩存的key factory = new Factory(key, parameter, subKey, valuesMap); // 這個工廠是實現Supplier<V>Factory的get實現中調用valueFactory的apply即ProxyClassFactory // //#apply,并最終返回了value value = Objects.requireNonNull(valueFactory.apply(key, parameter));
subkey只是中間的關聯變量,不需要關注,只需要關注value也就是代理類的生成。生成入口
ProxyClassFactory#apply(ClassLoader loader, Class<?>[] interfaces)
所有代理類的字節碼定義都在該方法中:主要如下
String proxyName = proxyPkg + proxyClassNamePrefix + num; // com.sun.proxy.+$Proxy+num --->com.sun.proxy.$Proxy0
proxyPkg在接口的訪問修飾符是public時=“com.sun.proxy“,否則=被代理類的包名
proxyClassNamePrefix=”$Proxy“
num為原子遞增AtomicLong,與生成代理類的個數相關
接口訪問修飾符 int accessFlags = Modifier.PUBLIC | Modifier.FINAL 即 public final
非public接口會重寫為final
所有方法都是 public final
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
此時新生成的代理類已經加載到JVM中去了
// 是否生成文件屬性讀取 private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles")); 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; } 字節碼生成,忽略部分代碼 private byte[] generateClassFile() { // equals、toString、hashCode 重寫 this.addProxyMethod(hashCodeMethod, Object.class); this.addProxyMethod(equalsMethod, Object.class); this.addProxyMethod(toStringMethod, Object.class); // 接口所有方法重寫 Class[] var1 = this.interfaces; int var2 = var1.length; int var3; Class var4; for(var3 = 0; var3 < var2; ++var3) { var4 = var1[var3]; Method[] var5 = var4.getMethods(); int var6 = var5.length; for(int var7 = 0; var7 < var6; ++var7) { Method var8 = var5[var7]; this.addProxyMethod(var8, var4); } } // ... ignore ... }
關于JDK動態代理的原理是什么問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。