開篇
Java 的代理就是客戶類不再直接和委托類打交道, 而是通過一個中間層來訪問, 這個中間層就是代理海诲。為啥要這樣呢, 是因為使用代理有 2 個優(yōu)勢:
可以隱藏委托類的實現(xiàn)
可以實現(xiàn)客戶與委托類之間的解耦, 在不修改委托類代碼的情況下能夠做一些額外的處理
我們舉個很常見的例子: 工廠會生產(chǎn)很多的玩具, 但是我們買玩具都是到商店買的, 而不是到工廠去買的, 工廠怎么生產(chǎn)我們并不關(guān)心, 我們只知道到商店可以買到自己想要的玩具,并且檩互,如果我們需要送人的話商店可以把這些玩具使用禮品盒包裝特幔。這個工廠就是委托類, 商店就是代理類, 我們就是客戶類。
在 Java 中我們有很多場景需要使用代理類, 比如遠程 RPC 調(diào)用的時候我們就是通過代理類去實現(xiàn)的, 還有 Spring 的 AOP 切面中我們也是為切面生成了一個代理類等等闸昨。
代理類主要分為靜態(tài)代理蚯斯、JDK 動態(tài)代理和 CGLIB 動態(tài)代理,它們各有優(yōu)缺點饵较,沒有最好的, 存在就是有意義的拍嵌,在不同的場景下它們會有不同的用武之地。
1. Java 靜態(tài)代理
首先, 定義接口和接口的實現(xiàn)類, 然后定義接口的代理對象, 將接口的實例注入到代理對象中, 然后通過代理對象去調(diào)用真正的實現(xiàn)類告抄,實現(xiàn)過程非常簡單也比較容易理解, 靜態(tài)代理的代理關(guān)系在編譯期間就已經(jīng)確定了的撰茎。它適合于代理類較少且確定的情況。它可實現(xiàn)在怒修改委托類代碼的情況下做一些額外的處理打洼,比如包裝禮盒,實現(xiàn)客戶類與委托類的解耦逆粹。缺點是只適用委托方法少的情況下, 試想一下如果委托類有幾百上千個方法, 豈不是很難受, 要在代理類中寫一堆的代理方法募疮。這個需求動態(tài)代理可以搞定
// 委托接口
public interface IHelloService {
/**
* 定義接口方法
* @param userName
* @return
*/
String sayHello(String userName);
}
// 委托類實現(xiàn)
public class HelloService implements IHelloService {
@Override
public String sayHello(String userName) {
System.out.println("helloService" + userName);
return "HelloService" + userName;
}
}
// 代理類
public class StaticProxyHello implements IHelloService {
private IHelloService helloService = new HelloService();
@Override
public String sayHello(String userName) {
/** 代理對象可以在此處包裝一下*/
System.out.println("代理對象包裝禮盒...");
return helloService.sayHello(userName);
}
}
// 測試靜態(tài)代理類
public class MainStatic {
public static void main(String[] args) {
StaticProxyHello staticProxyHello = new StaticProxyHello();
staticProxyHello.sayHello("isole");
}
}
2. 動態(tài)代理技術(shù)
代理類在程序運行時創(chuàng)建的代理方式被成為 動態(tài)代理。在了解動態(tài)代理之前, 我們先簡回顧一下 JVM 的類加載機制中的加載階段要做的三件事情
( 附 Java 中的類加載器 )
通過一個類的全名或其它途徑來獲取這個類的二進制字節(jié)流
將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)
在內(nèi)存中生成一個代表這個類的 Class 對象, 作為方法區(qū)中對這個類訪問的入口
而我們要說的動態(tài)代理僻弹,主要就發(fā)生在第一個階段, 這個階段類的二進制字節(jié)流的來源可以有很多, 比如 zip 包阿浓、網(wǎng)絡(luò)、運行時計算生成蹋绽、其它文件生成 (JSP)芭毙、數(shù)據(jù)庫獲取。其中運行時計算生成就是我們所說的動態(tài)代理技術(shù)卸耘,在 Proxy 類中, 就是運用了 ProxyGenerator.generateProxyClass 來為特定接口生成形式為 *$Proxy 的代理類的二進制字節(jié)流退敦。所謂的動態(tài)代理就是想辦法根據(jù)接口或者目標(biāo)對象計算出代理類的字節(jié)碼然后加載進 JVM 中。實際計算的情況會很復(fù)雜蚣抗,我們借助一些諸如 JDK 動態(tài)代理實現(xiàn)侈百、CGLIB 第三方庫來完成的
另一方面為了讓生成的代理類與目標(biāo)對象 (就是委托類) 保持一致, 我們有 2 種做法:通過接口的 JDK 動態(tài)代理 和通過繼承類的 CGLIB 動態(tài)代理。(還有一個使用了 ASM 框架的 javassist 太復(fù)雜了,我還沒研究過, 這里TODO下)
3. JDK 動態(tài)代理
在 Java 的動態(tài)代理中, 主要涉及 2 個類,java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler
我們需要一個實現(xiàn) InvocationHandler 接口的中間類, 這個接口只有一個方法 invoke 方法, 方法的每個參數(shù)的注釋如下代碼钝域。
我們對處理類中的所有方法的調(diào)用都會變成對 invoke 方法的調(diào)用讽坏,這樣我們可以在 invoke 方法中添加統(tǒng)一的處理邏輯(也可以根據(jù) method 參數(shù)判斷是哪個方法)。中間類 (實現(xiàn)了 InvocationHandler 的類) 有一個委托類對象引用, 在 Invoke 方法中調(diào)用了委托類對象的相應(yīng)方法例证,通過這種聚合的方式持有委托類對象引用路呜,把外部對 invoke 的調(diào)用最終都轉(zhuǎn)為對委托類對象的調(diào)用。
實際上织咧,中間類與委托類構(gòu)成了靜態(tài)代理關(guān)系胀葱,在這個關(guān)系中,中間類是代理類烦感,委托類是委托類巡社。然后代理類與中間類也構(gòu)成一個靜態(tài)代理關(guān)系,在這個關(guān)系中手趣,中間類是委托類晌该,代理類是代理類。也就是說绿渣,動態(tài)代理關(guān)系由兩組靜態(tài)代理關(guān)系組成朝群,這就是動態(tài)代理的原理。
public interface InvocationHandler {
/**
* 調(diào)用處理
* @param proxy 代理類對象
* @param methon 標(biāo)識具體調(diào)用的是代理類的哪個方法
* @param args 代理類方法的參數(shù)
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
Demo 如下:
// 委托類接口
public interface IHelloService {
/**
* 方法1
* @param userName
* @return
*/
String sayHello(String userName);
/**
* 方法2
* @param userName
* @return
*/
String sayByeBye(String userName);
}
// 委托類
public class HelloService implements IHelloService {
@Override
public String sayHello(String userName) {
System.out.println(userName + " hello");
return userName + " hello";
}
@Override
public String sayByeBye(String userName) {
System.out.println(userName + " ByeBye");
return userName + " ByeBye";
}
}
// 中間類
public class JavaProxyInvocationHandler implements InvocationHandler {
/**
* 中間類持有委托類對象的引用,這里會構(gòu)成一種靜態(tài)代理關(guān)系
*/
private Object obj ;
/**
* 有參構(gòu)造器,傳入委托類的對象
* @param obj 委托類的對象
*/
public JavaProxyInvocationHandler(Object obj){
this.obj = obj;
}
/**
* 動態(tài)生成代理類對象,Proxy.newProxyInstance
* @return 返回代理類的實例
*/
public Object newProxyInstance() {
return Proxy.newProxyInstance(
//指定代理對象的類加載器
obj.getClass().getClassLoader(),
//代理對象需要實現(xiàn)的接口中符,可以同時指定多個接口
obj.getClass().getInterfaces(),
//方法調(diào)用的實際處理者姜胖,代理對象的方法調(diào)用都會轉(zhuǎn)發(fā)到這里
this);
}
/**
*
* @param proxy 代理對象
* @param method 代理方法
* @param args 方法的參數(shù)
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke before");
Object result = method.invoke(obj, args);
System.out.println("invoke after");
return result;
}
}
// 測試動態(tài)代理類
public class MainJavaProxy {
public static void main(String[] args) {
JavaProxyInvocationHandler proxyInvocationHandler = new JavaProxyInvocationHandler(new HelloService());
IHelloService helloService = (IHelloService) proxyInvocationHandler.newProxyInstance();
helloService.sayByeBye("paopao");
helloService.sayHello("yupao");
}
}
在上面的測試動態(tài)代理類中, 我們調(diào)用 Proxy 類的 newProxyInstance 方法來獲取一個代理類實例。這個代理類實現(xiàn)了我們指定的接口并且會把方法調(diào)用分發(fā)到指定的調(diào)用處理器淀散。
首先通過 newProxyInstance 方法獲取代理類的實例, 之后就可以通過這個代理類的實例調(diào)用代理類的方法右莱,對代理類的方法調(diào)用都會調(diào)用中間類 (實現(xiàn)了 invocationHandle 的類) 的 invoke 方法,在 invoke 方法中我們調(diào)用委托類的對應(yīng)方法档插,然后加上自己的處理邏輯慢蜓。
java 動態(tài)代理最大的特點就是動態(tài)生成的代理類和委托類實現(xiàn)同一個接口。java 動態(tài)代理其實內(nèi)部是通過反射機制實現(xiàn)的郭膛,也就是已知的一個對象晨抡,在運行的時候動態(tài)調(diào)用它的方法,并且調(diào)用的時候還可以加一些自己的邏輯在里面则剃。(附: Java 反射)
3.2 Proxy.newProxyInstance 源碼閱讀
上面說過, Proxy.newProxyInstance 通過反射機制用來動態(tài)生成代理類對象, 為接口創(chuàng)建一個代理類耘柱,這個代理類實現(xiàn)這個接口。具體源碼如下:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
// 檢查空指針
Objects.requireNonNull(h);
// 用原型實例指定創(chuàng)建對象的種類,并且通過拷貝這些原型創(chuàng)建新的對象
final Class<?>[] intfs = interfaces.clone();
// 獲取系統(tǒng)的安全接口,不為空的話需要驗證是否允許訪問這種關(guān)系的代理訪問
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* 生成代理類 Class,通過類加載器和接口
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* 通過構(gòu)造器來創(chuàng)建實例
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//獲取所有的構(gòu)造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
// 構(gòu)造器不是public的話需要設(shè)置可以訪問
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 返回創(chuàng)建的代理類Class的實例對象
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
4. CGLIB 動態(tài)代理
JDK 動態(tài)代理依賴接口實現(xiàn)棍现,而當(dāng)我們只有類沒有接口的時候就需要使用另一種動態(tài)代理技術(shù) CGLIB 動態(tài)代理调煎。首先 CGLIB 動態(tài)代理是第三方框架實現(xiàn)的,在 maven 工程中我們需要引入 cglib 的包, 如下:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>
CGLIB 代理是針對類來實現(xiàn)代理的轴咱,原理是對指定的委托類生成一個子類并重寫其中業(yè)務(wù)方法來實現(xiàn)代理汛蝙。代理類對象是由 Enhancer 類創(chuàng)建的烈涮。CGLIB 創(chuàng)建動態(tài)代理類的模式是:
查找目標(biāo)類上的所有非 final 的 public 類型的方法 (final 的不能被重寫)
將這些方法的定義轉(zhuǎn)成字節(jié)碼
將組成的字節(jié)碼轉(zhuǎn)換成相應(yīng)的代理的 Class 對象然后通過反射獲得代理類的實例對象
實現(xiàn) MethodInterceptor 接口, 用來處理對代理類上所有方法的請求
// 委托類,是一個簡單類
public class CglibHelloClass {
/**
* 方法1
* @param userName
* @return
*/
public String sayHello(String userName){
System.out.println("目標(biāo)對象的方法執(zhí)行了");
return userName + " sayHello";
}
public String sayByeBye(String userName){
System.out.println("目標(biāo)對象的方法執(zhí)行了");
return userName + " sayByeBye";
}
}
/**
* CglibInterceptor 用于對方法調(diào)用攔截以及回調(diào)
*
*/
public class CglibInterceptor implements MethodInterceptor {
/**
* CGLIB 增強類對象,代理類對象是由 Enhancer 類創(chuàng)建的窖剑,
* Enhancer 是 CGLIB 的字節(jié)碼增強器坚洽,可以很方便的對類進行拓展
*/
private Enhancer enhancer = new Enhancer();
/**
*
* @param obj 被代理的對象
* @param method 代理的方法
* @param args 方法的參數(shù)
* @param proxy CGLIB方法代理對象
* @return cglib生成用來代替Method對象的一個對象,使用MethodProxy比調(diào)用JDK自身的Method直接執(zhí)行方法效率會有提升
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("方法調(diào)用之前");
Object o = proxy.invokeSuper(obj, args);
System.out.println("方法調(diào)用之后");
return o;
}
/**
* 使用動態(tài)代理創(chuàng)建一個代理對象
* @param c
* @return
*/
public Object newProxyInstance(Class<?> c) {
/**
* 設(shè)置產(chǎn)生的代理對象的父類,增強類型
*/
enhancer.setSuperclass(c);
/**
* 定義代理邏輯對象為當(dāng)前對象西土,要求當(dāng)前對象實現(xiàn) MethodInterceptor 接口
*/
enhancer.setCallback(this);
/**
* 使用默認無參數(shù)的構(gòu)造函數(shù)創(chuàng)建目標(biāo)對象,這是一個前提,被代理的類要提供無參構(gòu)造方法
*/
return enhancer.create();
}
}
//測試類
public class MainCglibProxy {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
CglibHelloClass cglibHelloClass = (CglibHelloClass) cglibProxy.newProxyInstance(CglibHelloClass.class);
cglibHelloClass.sayHello("isole");
cglibHelloClass.sayByeBye("sss");
}
}
對于需要被代理的類讶舰,它只是動態(tài)生成一個子類以覆蓋非 final 的方法,同時綁定鉤子回調(diào)自定義的攔截器需了。值得說的是跳昼,它比 JDK 動態(tài)代理還要快。值得注意的是肋乍,我們傳入目標(biāo)類作為代理的父類鹅颊。不同于 JDK 動態(tài)代理,我們不能使用目標(biāo)對象來創(chuàng)建代理墓造。目標(biāo)對象只能被 CGLIB 創(chuàng)建堪伍。在例子中,默認的無參構(gòu)造方法被使用來創(chuàng)建目標(biāo)對象觅闽。
總結(jié)
靜態(tài)代理比較容易理解, 需要被代理的類和代理類實現(xiàn)自同一個接口, 然后在代理類中調(diào)用真正實現(xiàn)類, 并且靜態(tài)代理的關(guān)系在編譯期間就已經(jīng)確定了帝雇。而動態(tài)代理的關(guān)系是在運行期間確定的。靜態(tài)代理實現(xiàn)簡單蛉拙,適合于代理類較少且確定的情況尸闸,而動態(tài)代理則給我們提供了更大的靈活性。
JDK 動態(tài)代理所用到的代理類在程序調(diào)用到代理類對象時才由 JVM 真正創(chuàng)建孕锄,JVM 根據(jù)傳進來的 業(yè)務(wù)實現(xiàn)類對象 以及 方法名 吮廉,動態(tài)地創(chuàng)建了一個代理類的 class 文件并被字節(jié)碼引擎執(zhí)行,然后通過該代理類對象進行方法調(diào)用畸肆。我們需要做的茧痕,只需指定代理類的預(yù)處理、調(diào)用后操作即可恼除。
靜態(tài)代理和動態(tài)代理都是基于接口實現(xiàn)的, 而對于那些沒有提供接口只是提供了實現(xiàn)類的而言, 就只能選擇 CGLIB 動態(tài)代理了
JDK 動態(tài)代理和 CGLIB 動態(tài)代理的區(qū)別
JDK 動態(tài)代理基于 Java 反射機制實現(xiàn), 必須要實現(xiàn)了接口的業(yè)務(wù)類才能用這種方法生成代理對象。
CGLIB 動態(tài)代理基于 ASM 框架通過生成業(yè)務(wù)類的子類來實現(xiàn)曼氛。
JDK 動態(tài)代理的優(yōu)勢是最小化依賴關(guān)系豁辉,減少依賴意味著簡化開發(fā)和維護并且有 JDK 自身支持。還可以平滑進行 JDK 版本升級舀患,代碼實現(xiàn)簡單徽级。基于 CGLIB 框架的優(yōu)勢是無須實現(xiàn)接口聊浅,達到代理類無侵入餐抢,我們只需操作我們關(guān)系的類现使,不必為其它相關(guān)類增加工作量,性能比較高旷痕。
描述代理的幾種實現(xiàn)方式? 分別說出優(yōu)缺點?
代理可以分為 "靜態(tài)代理" 和 "動態(tài)代理"碳锈,動態(tài)代理又分為 "JDK 動態(tài)代理" 和 "CGLIB 動態(tài)代理" 實現(xiàn)。
靜態(tài)代理:代理對象和實際對象都繼承了同一個接口欺抗,在代理對象中指向的是實際對象的實例售碳,這樣對外暴露的是代理對象而真正調(diào)用的是 Real Object.
優(yōu)點:可以很好的保護實際對象的業(yè)務(wù)邏輯對外暴露,從而提高安全性绞呈。*
缺點:不同的接口要有不同的代理類實現(xiàn)贸人,會很冗余
JDK 動態(tài)代理:
為了解決靜態(tài)代理中,生成大量的代理類造成的冗余佃声;
JDK 動態(tài)代理只需要實現(xiàn) InvocationHandler 接口艺智,重寫 invoke 方法便可以完成代理的實現(xiàn),
jdk 的代理是利用反射生成代理類 Proxyxx.class 代理類字節(jié)碼圾亏,并生成對象
jdk 動態(tài)代理之所以只能代理接口是因為代理類本身已經(jīng) extends 了 Proxy十拣,而 java 是不允許多重繼承的,但是允許實現(xiàn)多個接口
優(yōu)點:解決了靜態(tài)代理中冗余的代理實現(xiàn)類問題召嘶。
缺點:JDK 動態(tài)代理是基于接口設(shè)計實現(xiàn)的父晶,如果沒有接口,會拋異常弄跌。
CGLIB 代理:
由于 JDK 動態(tài)代理限制了只能基于接口設(shè)計甲喝,而對于沒有接口的情況,JDK 方式解決不了铛只;
CGLib 采用了非常底層的字節(jié)碼技術(shù)埠胖,其原理是通過字節(jié)碼技術(shù)為一個類創(chuàng)建子類,并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用淳玩,順勢織入橫切邏輯直撤,來完成動態(tài)代理的實現(xiàn)。
實現(xiàn)方式實現(xiàn) MethodInterceptor 接口蜕着,重寫 intercept 方法谋竖,通過 Enhancer 類的回調(diào)方法來實現(xiàn)。
但是 CGLib 在創(chuàng)建代理對象時所花費的時間卻比 JDK 多得多承匣,所以對于單例的對象蓖乘,因為無需頻繁創(chuàng)建對象,用 CGLib 合適韧骗,反之嘉抒,使用 JDK 方式要更為合適一些。
同時袍暴,由于 CGLib 由于是采用動態(tài)創(chuàng)建子類的方法些侍,對于 final 方法隶症,無法進行代理。
優(yōu)點:沒有接口也能實現(xiàn)動態(tài)代理岗宣,而且采用字節(jié)碼增強技術(shù)蚂会,性能也不錯。
缺點:技術(shù)實現(xiàn)相對難理解些狈定。