本文篇幅比較長(zhǎng)吠各,在確定您是否需要仔細(xì)閱讀本文前搂捧,可以先思考一下下面幾個(gè)問(wèn)題:
- 動(dòng)態(tài)代理是什么纯出?
- 如何實(shí)現(xiàn)動(dòng)態(tài)代理?
- 所有類(lèi)都能實(shí)現(xiàn)動(dòng)態(tài)代理嗎对人?
- 非目標(biāo)方法是否會(huì)被代理?
- 為什么 JDK 實(shí)現(xiàn)動(dòng)態(tài)代理必須要求被代理類(lèi)實(shí)現(xiàn)接口拂共?
- 為什么 CGLib 實(shí)現(xiàn)動(dòng)態(tài)代理要求被代理類(lèi)為非 final類(lèi)牺弄?
為了理解動(dòng)態(tài)代理,我們需要先了解代理模式是怎么回事宜狐。
代理模式
代理模式給某一個(gè)對(duì)象提供一個(gè)代理對(duì)象势告,并由代理對(duì)象控制對(duì)原對(duì)象的引用「Ш悖可以將代理模式理解為生活中常見(jiàn)的中介咱台,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...
通過(guò)代理模式俭驮,我們可以做到在不修改目標(biāo)對(duì)象的前提下,對(duì)目標(biāo)對(duì)象進(jìn)行功能擴(kuò)展回溺。但是上述靜態(tài)代理的不足之處在于需要事先寫(xiě)好相應(yīng)的代理類(lèi),而且在接口發(fā)生變化時(shí)需要對(duì)被代理類(lèi)及代理類(lèi)進(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 為類(lèi)加載器登澜,出于安全性,要求 loader 對(duì) interfaces 可見(jiàn)飘庄,通常使用被代理類(lèi)的ClassLoader。
- interfaces 為被代理對(duì)象需要實(shí)現(xiàn)的所有接口购撼。
- h為方法調(diào)用的實(shí)際處理者跪削,通過(guò) InvocationHandler 對(duì)被代理類(lèi)進(jìn)行拓展。
(ps:類(lèi)加載器后面會(huì)有專(zhuān)門(mén)介紹迂求。)
但是碾盐,等一下,為什么需要這三個(gè)參數(shù)呢揩局?是不是任意一個(gè) Java 類(lèi)都可以動(dòng)態(tài)代理呢毫玖?我們不妨深入看看 Proxy類(lèi)到底做了什么?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及接口獲取指定的代理類(lèi)的Class信息
Class<?> cl = getProxyClass0(loader, intfs);
try {
//在Proxy中 constructorParams被硬編碼為{InvocationHandler.class};
//獲取代理類(lèi)的參數(shù)為 InvocationHandler 的構(gòu)造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
//生成代理類(lèi)
return cons.newInstance(new Object[]{h});
} catch (XXException e) {
throw new XXException(e.toString(), e);
}
}
//從proxyClassCache中獲取代理類(lèi)的Class信息,如果沒(méi)有則根據(jù)classLoader付枫、ingerfaces生成加載并緩存
private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {
return proxyClassCache.get(loader, interfaces);
}
動(dòng)態(tài)編譯生成代理類(lèi)的代碼如下:
//通過(guò)反射動(dòng)態(tài)生成烹玉、編譯代理類(lèi),得到代理類(lèi)的字節(jié)碼數(shù)據(jù)
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName,interfaces, accessFlags);
try {
//動(dòng)態(tài)加載代理類(lè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)生成的代理類(lèi)的內(nèi)容,我們不妨輸出并使用反編譯工具查看動(dòng)態(tài)生成的代理類(lèi)的信息:
通過(guò)在程序運(yùn)行時(shí)設(shè)置:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
即可將生成的代理類(lèi)保存在項(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)代理是通過(guò)繼承 Proxy 類(lèi)装获,實(shí)現(xiàn)被代理類(lèi)的所有接口生成動(dòng)態(tài)代理類(lèi)瑞信。這也解釋了采用 JDK 動(dòng)態(tài)代理時(shí)為什么只能使用接口引用指向代理,而不能使用被代理的具體類(lèi)引用指向代理穴豫。
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)代理還重寫(xiě)了 Object 類(lèi)中的 hashCode绩郎、equals潘鲫、toString 三個(gè)方法。
為了更好地看出類(lèi)之間的依賴(lài)關(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)代理必須要求被代理類(lèi)實(shí)現(xiàn)接口,這是由于動(dòng)態(tài)代理動(dòng)態(tài)生成的代理類(lèi)需要繼承 Proxy 類(lèi)状植,而 Java 中只能單繼承的限制使得被代理類(lèi)必須實(shí)現(xiàn)接口才能實(shí)現(xiàn)動(dòng)態(tài)代理浊竟。
JDK 能夠很好地實(shí)現(xiàn)動(dòng)態(tài)代理,但是如果被代理的類(lèi)沒(méi)有實(shí)現(xiàn)接口就無(wú)法實(shí)現(xiàn)動(dòng)態(tài)代理津畸,這時(shí)候我們就需要使用第三方工具來(lái)幫忙了振定。
CGLib
CGLib 是一個(gè)強(qiáng)大的高性能的代碼生成包,它可以在運(yùn)行期擴(kuò)展 Java 類(lèi)及實(shí)現(xiàn)Java接口肉拓、提供方法的攔截后频,因此被眾多 AOP 框架使用。CGLib 包的底層是通過(guò)使用字節(jié)碼處理框架 ASM 來(lái)轉(zhuǎn)換字節(jié)碼并生成新的類(lèi)暖途。
使用 CGLib 實(shí)現(xiàn)動(dòng)態(tài)代理也很簡(jiǎn)單卑惜,首先
創(chuàng)建Enhancer對(duì)象
設(shè)置被代理類(lèi)
回調(diào)對(duì)象(回調(diào)類(lèi)實(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è)置被代理類(lèi)
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;
}
}
//被代理類(lèi)
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)代理類(lèi)保存到文件中驻售,可以通過(guò)設(shè)置:
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E://CGLib//proxy//");
來(lái)導(dǎo)出動(dòng)態(tài)代理類(lè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()...
}
通過(guò)反編譯得到代碼可以看出欺栗,CGLib 是通過(guò)繼承被代理類(lèi) ConcreteSubject 實(shí)現(xiàn)動(dòng)態(tài)代理的毫痕,這也就要求被代理的類(lèi)不能是 final 類(lèi)征峦。此外,與 JDK 動(dòng)態(tài)代理相比消请,CGLib 不僅重寫(xiě)了 Object 類(lèi)的 hashCode栏笆、equals、toString方法梯啤,還重寫(xiě)了 clone 方法竖伯。
此外,值得注意的是創(chuàng)建回調(diào)對(duì)象時(shí)因宇,采用實(shí)現(xiàn) MethodInterceptor 接口與 InvocationHandler 接口這兩種方式除了是否需要額外創(chuàng)建被代理對(duì)象以及方法調(diào)用的差異外七婴,還有一個(gè)小細(xì)節(jié):采用實(shí)現(xiàn) InvocationHandler 接口的方式生成的代理類(lèi)在調(diào)用的方法內(nèi)部如果還調(diào)用該代理類(lèi)的其他成員方法時(shí),會(huì)對(duì)被調(diào)用的其他方法進(jìn)行代理察滑,而采用 MethodInterceptor 接口方式不會(huì)打厘。(ps: JDK 動(dòng)態(tài)代理也不會(huì)對(duì)非目標(biāo)方法進(jìn)行代理。)
為被代理類(lèi)添加兩個(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 |
---|---|---|
是否依賴(lài)被代理對(duì)象實(shí)例 | 不依賴(lài) | 依賴(lài) |
目標(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 類(lèi)) | 非final類(lèi) |
代理類(lèi)生成方式 | 繼承 Proxy户盯,實(shí)現(xiàn)被代理類(lèi)的所有接口 | 繼承被代理類(lèi),實(shí)現(xiàn) Factory 接口 |
非目標(biāo)方法是否進(jìn)行代理 | 不進(jìn)行代理 | 可通過(guò) InvocationHandler 進(jìn)行代理 |
至此饲化,動(dòng)態(tài)代理兩種實(shí)現(xiàn)方式及其原理已經(jīng)介紹完畢莽鸭,在理解了相關(guān)原理后,我們完全可以通過(guò)反射及Java動(dòng)態(tài)編譯技術(shù)實(shí)現(xiàn)動(dòng)態(tài)代理吃靠。
既然JDK動(dòng)態(tài)代理要求被代理類(lèi)必須實(shí)現(xiàn)接口硫眨,而CGLib要求被代理類(lèi)不能是final類(lèi),那么能不能為沒(méi)有實(shí)現(xiàn)接口的final類(lèi)進(jìn)行動(dòng)態(tài)代理呢巢块?
答案是不能礁阁,但是可以通過(guò)反射來(lái)實(shí)現(xiàn)類(lèi)似動(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í)通過(guò) 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