細說JDK動態代理的實現原理
來源:blog.csdn.net/mhmyqn/article/details/48474815
關於JDK的動態代理,最為人熟知的可能要數Spring AOP的實現,預設情況下,Spring AOP的實現對於介面來說就是使用的JDK的動態代理來實現的,而對於類的代理使用CGLIB來實現。那麼,什麼是JDK的動態代理呢?
JDK的動態代理,就是在程式執行的過程中,根據被代理的介面來動態生成代理類的class檔案,並載入執行的過程。JDK從1.3開始支援動態代理。那麼JDK是如何生成動態代理的呢?JDK動態代理為什麼不支援類的代理,只支援介面的代理?
首先來看一下如何使用JDK動態代理。JDK提供了java.lang.reflect.Proxy類來實現動態代理的,可通過它的newProxyInstance來獲得代理實現類。同時對於代理的介面的實際處理,是一個java.lang.reflect.InvocationHandler,它提供了一個invoke方法供實現者提供相應的代理邏輯的實現。可以對實際的實現進行一些特殊的處理,像Spring AOP中的各種advice。下面來看看如何使用。
被代理的介面:
package com.mikan.proxy;
public interface HelloWorld {
void sayHello(String name);
}
介面的實現類:
package com.mikan.proxy;
public class HelloWorldImpl implements HelloWorld {
@Override
public void sayHello(String name) {
System.out.println("Hello " + name);
}
}
實現一個java.lang.reflect.InvocationHandler:
package com.mikan.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class CustomInvocationHandler implements InvocationHandler {
private Object target;
public CustomInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before invocation");
Object retVal = method.invoke(target, args);
System.out.println("After invocation");
return retVal;
}
}
使用代理:
package com.mikan.proxy;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) throws Exception {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
CustomInvocationHandler handler = new CustomInvocationHandler(new HelloWorldImpl());
HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance(
ProxyTest.class.getClassLoader(),
new Class[]{HelloWorld.class},
handler);
proxy.sayHello("Mikan");
}
}
執行的輸出結果:
localhost:classes mikan$ java com/mikan/proxy/ProxyTest
Before invocation
Hello Mikan
After invocation
從上面可以看出,JDK的動態代理使用起來非常簡單,但是隻知道如何使用是不夠的,知其然,還需知其所以然。所以要想搞清楚它的實現,那麼得從原始碼入手。這裡的原始碼是1.7.0_79。首先來看看它是如何生成代理類的:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {
if (h == null) {
throw new NullPointerException();
}
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
// 這裡是生成class的地方
Class<?> cl = getProxyClass0(loader, intfs);
// 使用我們實現的InvocationHandler作為引數呼叫構造方法來獲得代理類的例項
try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
return newInstance(cons, ih);
}
});
} else {
return newInstance(cons, ih);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
}
}
其中newInstance只是呼叫Constructor.newInstance來構造相應的代理類例項,這裡重點是看getProxyClass0這個方法的實現:
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
// 代理的介面數量不能超過65535(沒有這種變態吧)
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// JDK對代理進行了快取,如果已經存在相應的代理類,則直接返回,否則才會通過ProxyClassFactory來建立代理
return proxyClassCache.get(loader, interfaces);
}
其中代理快取是使用WeakCache實現的,如下:
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
具體的快取邏輯這裡暫不關心,只需要關心ProxyClassFactory是如何生成代理類的,ProxyClassFactory是Proxy的一個靜態內部類,實現了WeakCache的內部介面BiFunction的apply方法:
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
// 所有代理類名字的字首
private static final String proxyClassNamePrefix = "$Proxy";
// 用於生成代理類名字的計數器
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
// 省略驗證代理介面的程式碼……
String proxyPkg = null; // 生成的代理類的包名
// 對於非公共介面,代理類的包名與介面的相同
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
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");
}
}
}
// 對於公共介面的包名,預設為com.sun.proxy
if (proxyPkg == null) {
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
// 獲取計數
long num = nextUniqueNumber.getAndIncrement();
// 預設情況下,代理類的完全限定名為:com.sun.proxy.$Proxy0,com.sun.proxy.$Proxy1……依次遞增
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 這裡才是真正的生成代理類的位元組碼的地方
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
try {
// 根據二進位制位元組碼返回相應的Class例項
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
ProxyGenerator是sun.misc包中的類,它沒有開源,但是可以反編譯來一探究竟:
public static byte[] generateProxyClass(final String var0, Class[] var1) {
ProxyGenerator var2 = new ProxyGenerator(var0, var1);
final byte[] var3 = var2.generateClassFile();
// 這裡根據引數配置,決定是否把生成的位元組碼(.class檔案)儲存到本地磁碟,我們可以通過把相應的class檔案儲存到本地,再反編譯來看看具體的實現,這樣更直觀
if(saveGeneratedFiles) {
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
try {
FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class");
var1.write(var3);
var1.close();
return null;
} catch (IOException var2) {
throw new InternalError("I/O exception saving generated file: " + var2);
}
}
});
}
return var3;
}
saveGeneratedFiles這個屬性的值從哪裡來呢:
private static final boolean saveGeneratedFiles = ((Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();
GetBooleanAction實際上是呼叫Boolean.getBoolean(propName)來獲得的,而Boolean.getBoolean(propName)呼叫了System.getProperty(name),所以我們可以設定sun.misc.ProxyGenerator.saveGeneratedFiles這個系統屬性為true來把生成的class儲存到本地檔案來檢視。
這裡要注意,當把這個屬性設定為true時,生成的class檔案及其所在的路徑都需要提前建立,否則會丟擲FileNotFoundException異常。如:
Exception in thread "main" java.lang.InternalError: I/O exception saving generated file: java.io.FileNotFoundException: com/sun/proxy/$Proxy0.class (No such file or directory)
at sun.misc.ProxyGenerator$1.run(ProxyGenerator.java:336)
at sun.misc.ProxyGenerator$1.run(ProxyGenerator.java:327)
at java.security.AccessController.doPrivileged(Native Method)
at sun.misc.ProxyGenerator.generateProxyClass(ProxyGenerator.java:326)
at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:672)
at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:592)
at java.lang.reflect.WeakCache$Factory.get(WeakCache.java:244)
at java.lang.reflect.WeakCache.get(WeakCache.java:141)
at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:455)
at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:738)
at com.mikan.proxy.ProxyTest.main(ProxyTest.java:15)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
即我們要在執行當前main方法的路徑下建立com/sun/proxy目錄,並建立一個$Proxy0.class檔案,才能夠正常執行並儲存class檔案內容。
反編譯$Proxy0.class檔案,如下所示:
package com.sun.proxy;
import com.mikan.proxy.HelloWorld;
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 HelloWorld {
private static Method m1;
private static Method m3;
private static Method m0;
private static Method m2;
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 localError) {
throw localError;
}
catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void sayHello(String paramString) {
try {
this.h.invoke(this, m3, new Object[] { paramString });
return;
}
catch (Error|RuntimeException localError) {
throw localError;
}
catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode() {
try {
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError) {
throw localError;
}
catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError) {
throw localError;
}
catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("com.mikan.proxy.HelloWorld").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException) {
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException) {
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
可以看到,動態生成的代理類有如下特性:
繼承了Proxy類,實現了代理的介面,由於java不能多繼承,這裡已經繼承了Proxy類了,不能再繼承其他的類,所以JDK的動態代理不支援對實現類的代理,只支援介面的代理。
提供了一個使用InvocationHandler作為引數的構造方法。
生成靜態程式碼塊來初始化介面中方法的Method物件,以及Object類的equals、hashCode、toString方法。
重寫了Object類的equals、hashCode、toString,它們都只是簡單的呼叫了InvocationHandler的invoke方法,即可以對其進行特殊的操作,也就是說JDK的動態代理還可以代理上述三個方法。
代理類實現代理介面的sayHello方法中,只是簡單的呼叫了InvocationHandler的invoke方法,我們可以在invoke方法中進行一些特殊操作,甚至不呼叫實現的方法,直接返回。
至此JDK動態代理的實現原理就分析的差不多了。同時我們可以想像一下Spring AOP提供的各種攔截該如何實現,就已經很明瞭了,如下所示:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// BeforeAdvice
Object retVal = null;
try {
// AroundAdvice
retVal = method.invoke(target, args);
// AroundAdvice
// AfterReturningAdvice
}
catch (Throwable e) {
// AfterThrowingAdvice
}
finally {
// AfterAdvice
}
return retVal;
}
上面是對於Spring AOP使用JDK動態代理實現的基本框架程式碼,當然具體的實現肯定比這個複雜得多,但是基本原理不外乎如是。所以理解基本原理對於理解其他的程式碼也是很有好處的。
PS:如果覺得我的分享不錯,歡迎大家隨手點贊、轉發。
(完)
Java團長
專注於Java乾貨分享
掃描上方二維碼獲取更多Java乾貨
相關文章
- 【乾貨】JDK動態代理的實現原理以及如何手寫一個JDK動態代理JDK
- JDK動態代理實現原理詳解(原始碼分析)JDK原始碼
- Java-JDK動態代理(AOP)使用及實現原理分析JavaJDK
- spring aop原理 JDK動態代理和CGLIB動態代理SpringJDKCGLib
- 基於JDK的動態代理原理分析JDK
- 純手寫實現JDK動態代理JDK
- 【深度思考】聊聊JDK動態代理原理JDK
- CGLib動態代理原理及實現CGLib
- RPC核心實現原理-動態代理RPC
- Java動態代理 jdk和cglib的實現比較JavaJDKCGLib
- Spring的JDK動態代理如何實現的(原始碼解析)SpringJDK原始碼
- JDK 和 CGLib 實現動態代理和區別JDKCGLib
- JDK動態代理JDK
- Java代理(jdk靜態代理、動態代理和cglib動態代理)JavaJDKCGLib
- 看過Jdk動態代理類長啥樣嗎?Jdk動態代理原理原始碼一本到JDK原始碼
- 說說Android動態換膚實現原理吧Android
- JDK動態代理初探JDK
- JDK動態代理和CGLib代理JDKCGLib
- JDK動態代理和 CGLIB 代理JDKCGLib
- 淺析Spring中AOP的實現原理——動態代理Spring
- Java 動態代理原理圖解 (附:2種實現方式詳細對比)Java圖解
- 代理模式詳解:靜態代理、JDK動態代理與Cglib動態代理模式JDKCGLib
- 你必須會的 JDK 動態代理和 CGLIB 動態代理JDKCGLib
- 深挖JDK動態代理(一)JDK
- JDK動態代理詳解JDK
- 【Spring AOP】AOP 底層實現原理 —— 動態代理類的建立(JDK、CGlib)、工廠如何加工原始物件SpringJDKCGLib物件
- cglib動態代理和jdk動態代理的區別與應用CGLibJDK
- 深入理解靜態代理與JDK動態代理JDK
- 靜態代理和動態代理(jdk/cglib)詳解JDKCGLib
- 深挖JDK動態代理(二):JDK動態生成後的位元組碼分析JDK
- Java動態代理(JDK和cglib)JavaJDKCGLib
- Spring AOP --JDK動態代理方式SpringJDK
- 有點深度的聊聊JDK動態代理JDK
- 動態代理jdk和cglib的區別JDKCGLib
- Spring動態代理的生成-如何判斷是使用JDK動態代理還是CGlib代理SpringJDKCGLib
- Spring原始碼剖析5:JDK和cglib動態代理原理詳解Spring原始碼JDKCGLib
- 動態代理jdk的Proxy與spring的CGlibJDKSpringCGLib
- 13萬字詳細分析JDK中Stream的實現原理JDK