本文篇幅比較長(zhǎng),在確定您是否需要仔細(xì)閱讀本文前,可以先思考一下下面幾個(gè)問題:
- 動(dòng)態(tài)代理是什么?
- 如何實(shí)現(xiàn)動(dòng)態(tài)代理?
- 所有類都能實(shí)現(xiàn)動(dòng)態(tài)代理嗎?
- 非目標(biāo)方法是否會(huì)被代理?
- 為什么 JDK 實(shí)現(xiàn)動(dòng)態(tài)代理必須要求被代理類實(shí)現(xiàn)接口?
- 為什么 CGLib 實(shí)現(xiàn)動(dòng)態(tài)代理要求被代理類為非 final類?
為了理解動(dòng)態(tài)代理,我們需要先了解代理模式是怎么回事。
代理模式
代理模式給某一個(gè)對(duì)象提供一個(gè)代理對(duì)象,并由代理對(duì)象控制對(duì)原對(duì)象的引用。可以將代理模式理解為生活中常見的中介,UML 圖如下。

public interface Subject {
public void doOperation();
}
public class RealSubject implements Subject {
@Override
public void doOperation() {
System.out.println("RealSubject doOperation...");
}
}
public class Proxy implements Subject {
private Subject subject;
public Proxy(Subject subject) {
this.subject = subject;
}
@Override
public void doOperation() {
System.out.println("Proxy before RealSubject doOperation...");
subject.doOperation();
System.out.println("Proxy after RealSubject doOperation...");
}
}
public class Client {
public static void main(String[] args) {
Subject subject = new Proxy(new RealSubject());
subject.doOperation();
}
}
上述代碼輸出如下:
Proxy before RealSubject doOperation...
RealSubject doOperation...
Proxy after RealSubject doOperation...
通過代理模式,我們可以做到在不修改目標(biāo)對(duì)象的前提下,對(duì)目標(biāo)對(duì)象進(jìn)行功能擴(kuò)展。但是上述靜態(tài)代理的不足之處在于需要事先寫好相應(yīng)的代理類,而且在接口發(fā)生變化時(shí)需要對(duì)被代理類及代理類進(jìn)行修改,對(duì)此 Java 引入了動(dòng)態(tài)代理的概念。
動(dòng)態(tài)代理
與靜態(tài)代理需要事先構(gòu)建不同,動(dòng)態(tài)代理是動(dòng)態(tài)地在內(nèi)存中生成的。一般而言,動(dòng)態(tài)代理可以由 JDK 動(dòng)態(tài)代理及CGLib 動(dòng)態(tài)代理實(shí)現(xiàn)。
JDK動(dòng)態(tài)代理
使用JDK動(dòng)態(tài)代理只需要3步即可完成:
- 創(chuàng)建被代理的對(duì)象 RealSubject
- 創(chuàng)建被代理對(duì)象的處理對(duì)象,持有目標(biāo)(被代理)對(duì)象 JDKInvocationHandler
- 使用Proxy的靜態(tài)方法 newProxyInstance 創(chuàng)建代理對(duì)象
public class JDKInvocationHandler implements java.lang.reflect.InvocationHandler {
private Subject subject;
public JDKInvocationHandler(Subject subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDKProxy before RealSubject doOperation...");
Object ret = method.invoke(subject, args);
System.out.println("JDKProxy after RealSubject doOperation...");
return ret;
}
}
public class Client {
public static void main(String[] args) {
//創(chuàng)建被代理的對(duì)象 realSubject
RealSubject realSubject = new RealSubject();
//創(chuàng)建被代理對(duì)象的處理對(duì)象
InvocationHandler handler = new JDKInvocationHandler(realSubject);
//創(chuàng)建代理對(duì)象
Subject proxy = (Subject) Proxy.newProxyInstance(
RealSubject.class.getClassLoader(),
RealSubject.class.getInterfaces(),
handler);
//執(zhí)行相應(yīng)的方法
proxy.doOperation();
}
}
上述代碼輸出如下:
JDKProxy before RealSubject doOperation...
RealSubject doOperation...
JDKProxy after RealSubject doOperation...
其中,InvocationHandler 是 Java 自帶的接口,其定義如下:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
Proxy 靜態(tài)方法的定義如下:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
其中,
- loader 為類加載器,出于安全性,要求 loader 對(duì) interfaces 可見,通常使用被代理類的ClassLoader。
- interfaces 為被代理對(duì)象需要實(shí)現(xiàn)的所有接口。
- h為方法調(diào)用的實(shí)際處理者,通過 InvocationHandler 對(duì)被代理類進(jìn)行拓展。
(ps:類加載器后面會(huì)有專門介紹。)
但是,等一下,為什么需要這三個(gè)參數(shù)呢?是不是任意一個(gè) Java 類都可以動(dòng)態(tài)代理呢?我們不妨深入看看 Proxy類到底做了什么?Proxy.newProxyInstance 方法的主要內(nèi)容(刪除了安全檢測(cè)等內(nèi)容)如下:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException{
final Class<?>[] intfs = interfaces.clone();
//根據(jù)ClassLoader及接口獲取指定的代理類的Class信息
Class<?> cl = getProxyClass0(loader, intfs);
try {
//在Proxy中 constructorParams被硬編碼為{InvocationHandler.class};
//獲取代理類的參數(shù)為 InvocationHandler 的構(gòu)造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
//生成代理類
return cons.newInstance(new Object[]{h});
} catch (XXException e) {
throw new XXException(e.toString(), e);
}
}
//從proxyClassCache中獲取代理類的Class信息,如果沒有則根據(jù)classLoader、ingerfaces生成加載并緩存
private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {
return proxyClassCache.get(loader, interfaces);
}
動(dòng)態(tài)編譯生成代理類的代碼如下:
//通過反射動(dòng)態(tài)生成、編譯代理類,得到代理類的字節(jié)碼數(shù)據(jù)
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName,interfaces, accessFlags);
try {
//動(dòng)態(tài)加載代理類
return defineClass0(loader,proxyName,proxyClassFile,0,proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
至此,我們知道了 Proxy 的運(yùn)行邏輯,為了進(jìn)一步了解動(dòng)態(tài)生成的代理類的內(nèi)容,我們不妨輸出并使用反編譯工具查看動(dòng)態(tài)生成的代理類的信息:
通過在程序運(yùn)行時(shí)設(shè)置:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
即可將生成的代理類保存在項(xiàng)目根目錄下,路徑為:com/sun/proxy/$ProxyNum.class
使用Java Decompiler工具查看,結(jié)果如下:
package com.sun.proxy;
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 Subject {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}
@Override
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);
}
}
@Override
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);
}
}
@Override
public final void doOperation() {
try {
this.h.invoke(this, m3, null);
return;
} catch (Error | RuntimeException localError) {
throw localError;
} catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
@Override
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);
}
}
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("com.iqts.proxy.Subject").getMethod("doOperation", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
} catch (NoSuchMethodException localNoSuchMethodException) {
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
} catch (ClassNotFoundException localClassNotFoundException) {
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
到這里可以大概推測(cè)出,JDK 動(dòng)態(tài)代理是通過繼承 Proxy 類,實(shí)現(xiàn)被代理類的所有接口生成動(dòng)態(tài)代理類。這也解釋了采用 JDK 動(dòng)態(tài)代理時(shí)為什么只能使用接口引用指向代理,而不能使用被代理的具體類引用指向代理。
Subject proxy = (Subject) Proxy.newProxyInstance(RealSubject.class.getClassLoader(),
RealSubject.class.getInterfaces(), handler);//ok
//java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to RealSubject
RealSubject proxy = (RealSubject) Proxy.newProxyInstance(
RealSubject.class.getClassLoader(),
RealSubject.class.getInterfaces(), handler);
此外,除了實(shí)現(xiàn)了被代理所有接口中的方法外,JDK 動(dòng)態(tài)代理還重寫了 Object 類中的 hashCode、equals、toString 三個(gè)方法。
為了更好地看出類之間的依賴關(guān)系,上述代碼可以簡(jiǎn)化如下:
public interface Subject {
public void doOperation();
}
public class RealSubject implements Subject {
@Override
public void doOperation() {
System.out.println("RealSubject doOperation...");
}
}
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
public class Proxy implements java.io.Serializable {
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
this.h = h;
}
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h){
Class<?> cl = getProxyClass0(loader, intfs);
final Constructor<?> cons = cl.getConstructor(InvocationHandler.class);
return cons.newInstance(new Object[]{h});
}
}
public final class $Proxy0 extends Proxy implements Subject {
private static Method m3;
public $Proxy0(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}
@Override
public final void doOperation() {
try {
this.h.invoke(this, m3, null);
} catch (Error | RuntimeException localError) {
throw localError;
} catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
}
到這里我們就不難理解,為什么 JDK 實(shí)現(xiàn)動(dòng)態(tài)代理必須要求被代理類實(shí)現(xiàn)接口,這是由于動(dòng)態(tài)代理動(dòng)態(tài)生成的代理類需要繼承 Proxy 類,而 Java 中只能單繼承的限制使得被代理類必須實(shí)現(xiàn)接口才能實(shí)現(xiàn)動(dòng)態(tài)代理。
JDK 能夠很好地實(shí)現(xiàn)動(dòng)態(tài)代理,但是如果被代理的類沒有實(shí)現(xiàn)接口就無法實(shí)現(xiàn)動(dòng)態(tài)代理,這時(shí)候我們就需要使用第三方工具來幫忙了。
CGLib
CGLib 是一個(gè)強(qiáng)大的高性能的代碼生成包,它可以在運(yùn)行期擴(kuò)展 Java 類及實(shí)現(xiàn)Java接口、提供方法的攔截,因此被眾多 AOP 框架使用。CGLib 包的底層是通過使用字節(jié)碼處理框架 ASM 來轉(zhuǎn)換字節(jié)碼并生成新的類。
使用 CGLib 實(shí)現(xiàn)動(dòng)態(tài)代理也很簡(jiǎn)單,首先
創(chuàng)建Enhancer對(duì)象
設(shè)置被代理類
回調(diào)對(duì)象(回調(diào)類實(shí)現(xiàn) MethodInterceptor或InvocationHandler接口)
創(chuàng)建并設(shè)置回調(diào)對(duì)象
創(chuàng)建代理對(duì)象
public class CGLib {
public static void main(String[] args) {
// 創(chuàng)建Enhancer對(duì)象
Enhancer enhancer = new Enhancer();
// 設(shè)置被代理類
enhancer.setSuperclass(ConcreteSubject.class);
// 創(chuàng)建回調(diào)對(duì)象
//實(shí)現(xiàn) MethodInterceptor 接口
Callback callback = new CGLibMethodInterceptor();
//實(shí)現(xiàn) InvocationHandler 接口
Callback callback = new CGLibInvocationHandler(new ConcreteSubject());
// 設(shè)置回調(diào)對(duì)象
enhancer.setCallback(callback);
// 創(chuàng)建代理對(duì)象
ConcreteSubject subject = (ConcreteSubject) enhancer.create();
subject.doOperation();
}
}
//回調(diào)對(duì)象 實(shí)現(xiàn) MethodInterceptor
public class CGLibMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj,Method method,Object[] args,MethodProxy proxy)
throws Throwable {
Object ret = null;
System.out.println("CGLib before ConcreteSubject doOperation...");
ret = proxy.invokeSuper(obj, args);
System.out.println("CGLib after ConcreteSubject doOperation...");
return ret;
}
}
//回調(diào)對(duì)象 實(shí)現(xiàn) InvocationHandler
public class CGLibInvocationHandler implements InvocationHandler {
private Object realSubject;
public CGLibInvocationHandler(Object realSubject) {
super();
this.realSubject = realSubject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("CGLib before ConcreteSubject doOperation...");
Object ret = method.invoke(realSubject, args);
System.out.println("CGLib after ConcreteSubject doOperation...");
return ret;
}
}
//被代理類
public class ConcreteSubject {
public void doOperation() {
System.out.println("ConcreteSubject doOperation...");
}
}
輸出如下:
CGLib before ConcreteSubject doOperation...
ConcreteSubject doOperation...
CGLib after ConcreteSubject doOperation...
為了進(jìn)一步理解 CGLib 動(dòng)態(tài)代理的生成機(jī)制,我們不妨將生成的動(dòng)態(tài)代理類保存到文件中,可以通過設(shè)置:
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E://CGLib//proxy//");
來導(dǎo)出動(dòng)態(tài)代理類,使用Java Decompiler工具查看,結(jié)果如下:
public class ConcreteSubject$$EnhancerByCGLib$$7e8b8caf
extends ConcreteSubject implements Factory {
private MethodInterceptor CGLib$CALLBACK_0;
final void CGLib$doOperation$0() {
super.doOperation();
}
@Override
public final void doOperation(){
MethodInterceptor tmp4_1 = this.CGLib$CALLBACK_0;
if (tmp4_1 == null){
tmp4_1;
CGLib$BIND_CALLBACKS(this);
}
if (this.CGLib$CALLBACK_0 != null) {
return;
}
super.doOperation();
}
@Override
public final boolean equals(Object paramObject)...
@Override
public final String toString()...
@Override
public final int hashCode()...
@Override
public final Object clone()...
}
通過反編譯得到代碼可以看出,CGLib 是通過繼承被代理類 ConcreteSubject 實(shí)現(xiàn)動(dòng)態(tài)代理的,這也就要求被代理的類不能是 final 類。此外,與 JDK 動(dòng)態(tài)代理相比,CGLib 不僅重寫了 Object 類的 hashCode、equals、toString方法,還重寫了 clone 方法。
此外,值得注意的是創(chuàng)建回調(diào)對(duì)象時(shí),采用實(shí)現(xiàn) MethodInterceptor 接口與 InvocationHandler 接口這兩種方式除了是否需要額外創(chuàng)建被代理對(duì)象以及方法調(diào)用的差異外,還有一個(gè)小細(xì)節(jié):采用實(shí)現(xiàn) InvocationHandler 接口的方式生成的代理類在調(diào)用的方法內(nèi)部如果還調(diào)用該代理類的其他成員方法時(shí),會(huì)對(duì)被調(diào)用的其他方法進(jìn)行代理,而采用 MethodInterceptor 接口方式不會(huì)。(ps: JDK 動(dòng)態(tài)代理也不會(huì)對(duì)非目標(biāo)方法進(jìn)行代理。)
為被代理類添加兩個(gè)方法:
public class ConcreteSubject {
public static void fn() {
System.out.println("fn");
}
public void doOperation() {
System.out.println("ConcreteSubject doOperation...");
other();// InvocationHandler 同時(shí)會(huì)代理非目標(biāo)方法
fn();//靜態(tài)方法不代理
}
public void other() {
System.out.println("ConcreteSubject other...");
}
}
相應(yīng)的輸出如下:
invocationHandler
CGLib before RealSubject doOperation...
ConcreteSubject doOperation...
ConcreteSubject other...
fn
CGLib after RealSubject doOperation...
==================
methodInterceptor
Cglib before ConcreteSubject doOperation...
ConcreteSubject doOperation...
Cglib before ConcreteSubject doOperation...
ConcreteSubject other...
Cglib after ConcreteSubject doOperation...
fn
Cglib after ConcreteSubject doOperation...
CGLib 生成動(dòng)態(tài)代理的兩種方式的區(qū)別總結(jié)如下:
| 項(xiàng)目 | MethodInterceptor | InvocationHandler |
|---|---|---|
| 是否依賴被代理對(duì)象實(shí)例 | 不依賴 | 依賴 |
| 目標(biāo)方法執(zhí)行方式 | method.invokeSuper(proxy, args) | method.invoke(realSubject, args); |
| 非目標(biāo)方法是否進(jìn)行代理 | 不代理 | 代理 |
JDK 與 CGLib 動(dòng)態(tài)代理的區(qū)別
結(jié)合 JDK 動(dòng)態(tài)代理的實(shí)現(xiàn),可以得出下列區(qū)別:
| 項(xiàng)目 | JDK | CGLib |
|---|---|---|
| 被代理對(duì)象的要求 | 必須實(shí)現(xiàn)接口(可為 final 類) | 非final類 |
| 代理類生成方式 | 繼承 Proxy,實(shí)現(xiàn)被代理類的所有接口 | 繼承被代理類,實(shí)現(xiàn) Factory 接口 |
| 非目標(biāo)方法是否進(jìn)行代理 | 不進(jìn)行代理 | 可通過 InvocationHandler 進(jìn)行代理 |
至此,動(dòng)態(tài)代理兩種實(shí)現(xiàn)方式及其原理已經(jīng)介紹完畢,在理解了相關(guān)原理后,我們完全可以通過反射及Java動(dòng)態(tài)編譯技術(shù)實(shí)現(xiàn)動(dòng)態(tài)代理。
既然JDK動(dòng)態(tài)代理要求被代理類必須實(shí)現(xiàn)接口,而CGLib要求被代理類不能是final類,那么能不能為沒有實(shí)現(xiàn)接口的final類進(jìn)行動(dòng)態(tài)代理呢?
答案是不能,但是可以通過反射來實(shí)現(xiàn)類似動(dòng)態(tài)代理的功能,只需要將Proxy進(jìn)行改造即可,如:
public class MyProxy {
protected InvocationHandler invocationHandler;
public MyProxy(InvocationHandler invocationHandler) {
super();
this.invocationHandler = invocationHandler;
}
public static Object newProxyInstance(ClassLoader loader,
Class<?> clz, InvocationHandler h){
...
}
public Object invoke(String methodName, Object... args){
Class<?>[] parameterTypes = getParameterTypes(args);
Method method = this.getClass().getDeclaredMethod(methodName,parameterTypes);
return method.invoke(this, args);
}
}
使用時(shí)通過 MyProxy 的 invoke 方法實(shí)現(xiàn)被代理對(duì)象方法的調(diào)用:
MyProxy proxy = (MyProxy) MyProxy.newProxyInstance(
FinalSubject.class.getClassLoader(),
FinalSubject.class,
new FinalInvocationHandler(new FinalSubject()));
proxy.invoke("doOperation");
proxy.invoke("other",args);
完整項(xiàng)目可以參考本人的 github 小項(xiàng)目 DynamicProxy
update: 2019-03-10 11:04:25