詳解JDK動態(tài)代理和CGLib動態(tài)代理
代理模式
代理模式(Proxy Pattern)是23種設(shè)計模式中的一種,屬于結(jié)構(gòu)型設(shè)計模式。代理模式給某一個對象提供一個代理,并由代理對象控制原對象的引用。代理對象在客戶端和目標(biāo)對象之間起到中介作用。
舉個例子:你要去吃飯,你可以選擇自己在家做飯、吃飯、刷碗,所有的事情都自己做;也可以選擇去餐廳,自己只是吃飯,把做飯和刷碗的活兒都交給代理對象,也就是餐廳的工作人員。
下圖是代理模式的通用類圖。結(jié)合例子,就很容易理解了。
代理模式通用類圖
代理模式包含如下角色:
- Subject (抽象主題角色) 抽象主題角色聲明了真實主題和代理主題的共同接口,這樣一來在任何使用真實主題 的地方都可以使用代理主題。客戶端需要針對抽象主題角色進行編程。
- Proxy (代理主題角色) 代理主題角色內(nèi)部包含對真實主題的引用,從而可以在任何時候操作真實主題對象。 在代理主題角色中提供一個與真實主題角色相同的接口,以便在任何時候都可以替代真實主體。代理主題角色還可以控制對真實主題的使用,負(fù)責(zé)在需要的時候創(chuàng)建和刪除真實主題對象,并對真實主題對象的使用加以約束。代理角色通常在客戶端調(diào)用所引用的真實主題操作之前或之后還需要執(zhí)行其他操作,而不僅僅是單純的調(diào)用真實主題對象中的操作。
- RealSubject (真實主題角色) 真實主題角色定義了代理角色所代表的真實對象,在真實主題角色中實現(xiàn)了真實的業(yè)務(wù)操作,客戶端可以通過代理主題角色間接調(diào)用真實主題角色中定義的方法。
代理模式可以分為靜態(tài)代理和動態(tài)代理兩種類型,而動態(tài)代理中又分為JDK動態(tài)代理和CGLIB代理兩種。
JDK動態(tài)代理
在jdk的動態(tài)代理機制中,有幾個重要的角色:
- Interface:對于JDK Proxy,業(yè)務(wù)類是需要一個Interface的。
- Proxy:Proxy類是動態(tài)產(chǎn)生的,這個類在調(diào)用Proxy.newProxyInstance()方法之后,產(chǎn)生一個Proxy類的實例。實際上,這個Proxy類也是存在的,不僅僅是類的實例,這個Proxy類可以保存在硬盤上。
- Method:對于業(yè)務(wù)委托類的每個方法,現(xiàn)在Proxy類里面都不用靜態(tài)顯示出來。
- InvocationHandler:這個類在業(yè)務(wù)委托類執(zhí)行時,會先調(diào)用invoke方法。invoke方法再執(zhí)行想要的代理操作,可以實現(xiàn)對業(yè)務(wù)方法的再包裝。
(1)InvocationHandler
每一個動態(tài)代理類都必須要實現(xiàn)InvocationHandler這個接口,并且每個代理類的實例都關(guān)聯(lián)了一個handler,當(dāng)我們通過代理對象調(diào)用一個方法的時候,這個方法的調(diào)用就會被轉(zhuǎn)發(fā)為由InvocationHandler這個接口的 invoke 方法來進行調(diào)用。
InvocationHandler這個接口的唯一一個方法 invoke 方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
這個方法一共接受三個參數(shù),那么這三個參數(shù)分別代表如下:
- proxy:指代JDK動態(tài)生成的最終代理對象
- method:指代的是我們所要調(diào)用真實對象的某個方法的Method對象
- args:指代的是調(diào)用真實對象某個方法時接受的參數(shù)
(2)Proxy
Proxy這個類的作用就是用來動態(tài)創(chuàng)建一個代理對象的類,它提供了許多的方法,但是我們用的最多的就是newProxyInstance 這個方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException
這個方法的作用就是得到一個動態(tài)的代理對象,其接收三個參數(shù),我們來看看這三個參數(shù)所代表的含義:
- loader:ClassLoader對象,定義了由哪個ClassLoader來對生成的代理對象進行加載,即代理類的類加載器。
- interfaces:Interface對象的數(shù)組,表示的是我將要給我需要代理的對象提供一組什么接口,如果我提供了一組接口給它,那么這個代理對象就宣稱實現(xiàn)了該接口(多態(tài)),這樣我就能調(diào)用這組接口中的方法了。
- Handler:InvocationHandler對象,表示的是當(dāng)我這個動態(tài)代理對象在調(diào)用方法的時候,會關(guān)聯(lián)到哪一個InvocationHandler對象上。
所以我們所說的DynamicProxy(動態(tài)代理類)是這樣一種class:它是在運行時生成的class,在生成它時你必須提供一組interface給它,然后該class就宣稱它實現(xiàn)了這些 interface。這個DynamicProxy其實就是一個Proxy,它不會做實質(zhì)性的工作,在生成它的實例時你必須提供一個handler,由它接管實際的工作。
(3)代碼實現(xiàn)
被代理對象:
/**
* 抽象主題角色
*/
interface Subject {
void eat();
}
/**
* 真實主題角色 - 你自己 - 專注吃飯
*/
class YourSelf implements Subject{
@Override
public void eat() {
System.out.println("自己吃飯");
}
}
代理對象:
/**
* 代理主題角色 - 餐廳
* 每次生成動態(tài)代理類對象時都需要指定一個實現(xiàn)了InvocationHandler接口的調(diào)用處理器對象
*/
class JdkProxySubject implements InvocationHandler {
// 這個就是我們要代理的真實對象,也就是真正執(zhí)行業(yè)務(wù)邏輯的類
private Object target;
// 通過構(gòu)造方法傳入這個被代理對象
public JdkProxySubject(Object target) {
super();
this.target = target;
}
// 創(chuàng)建代理對象
public Object createProxy() {
// 1.得到目標(biāo)對象的類加載器
ClassLoader classLoader = target.getClass().getClassLoader();
// 2.得到目標(biāo)對象的實現(xiàn)接口
Class<?>[] interfaces = target.getClass().getInterfaces();
// 3.第三個參數(shù)需要一個實現(xiàn)invocationHandler接口的對象
Object newProxyInstance = Proxy.newProxyInstance(classLoader, interfaces, this);
return newProxyInstance;
}
// 當(dāng)代理對象調(diào)用真實對象的方法時,其會自動的跳轉(zhuǎn)到代理對象關(guān)聯(lián)的handler對象的invoke方法來進行調(diào)用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("餐廳工作人員做飯......");
Object invoke = method.invoke(target, args);
System.out.println("餐廳工作人員刷碗......");
return invoke;
}
}
測試類:
/**
* 測試類
* @author tianxiaopeng@hxy
* @date 2023/10/11 11:09 AM
*/
public class ProxyTest {
public static void main(String[] args) {
// 1.創(chuàng)建對象
YourSelf yourSelf = new YourSelf();
// 2.創(chuàng)建代理對象
JdkProxySubject proxy = new JdkProxySubject(yourSelf);
// 3.調(diào)用代理對象的增強方法,得到增強后的對象
Subject createProxy = (Subject) proxy.createProxy();
createProxy.eat();
}
}
CGLIB動態(tài)代理
JDK動態(tài)代理是通過重寫被代理對象實現(xiàn)的接口中的方法來實現(xiàn),而CGLIB是通過繼承被代理對象來實現(xiàn),和JDK動態(tài)代理需要實現(xiàn)指定接口一樣,CGLIB也要求代理對象必須要實現(xiàn)MethodInterceptor接口,并重寫其唯一的方法intercept。
CGLib采用了非常底層的字節(jié)碼技術(shù),其原理是通過字節(jié)碼技術(shù)為一個類創(chuàng)建子類,并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用,順勢織入橫切邏輯。(利用ASM開源包,對代理對象類的class文件加載進來,通過修改其字節(jié)碼生成子類來處理)
注意:因為CGLIB是通過繼承目標(biāo)類來重寫其方法來實現(xiàn)的,故而如果是final和private方法則無法被重寫,也就是無法被代理。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2</version>
</dependency>
(1)CGLib核心類
net.sf.cglib.proxy.Enhancer:主要增強類,通過字節(jié)碼技術(shù)動態(tài)創(chuàng)建委托類的子類實例。
Enhancer可能是CGLIB中最常用的一個類,和Java1.3動態(tài)代理中引入的Proxy類差不多。和Proxy不同的是,Enhancer既能夠代理普通的class,也能夠代理接口。Enhancer創(chuàng)建一個被代理對象的子類并且攔截所有的方法調(diào)用(包括從Object中繼承的toString和hashCode方法)。Enhancer不能夠攔截final方法,例如Object.getClass()方法,這是由于Java final方法語義決定的。基于同樣的道理,Enhancer也不能對fianl類進行代理操作。這也是Hibernate為什么不能持久化final class的原因。
net.sf.cglib.proxy.MethodInterceptor:常用的方法攔截器接口,需要實現(xiàn)intercept方法,實現(xiàn)具體攔截處理。
public java.lang.Object intercept(java.lang.Object obj,
java.lang.reflect.Method method,
java.lang.Object[] args,
MethodProxy proxy) throws java.lang.Throwable{}
- obj:動態(tài)生成的代理對象。
- method:實際調(diào)用的方法。
- args:調(diào)用方法入?yún)ⅰ?/li>
- net.sf.cglib.proxy.MethodProxy:java Method類的代理類,可以實現(xiàn)委托類對象的方法的調(diào)用;常用方法:methodProxy.invokeSuper(proxy, args);在攔截方法內(nèi)可以調(diào)用多次。
(2)CGLib代理實例
創(chuàng)建被代理類。
/**
* 真實主題角色 - 你自己 - 專注吃飯
*/
class YourSelf {
public void eat(){
System.out.println("自己吃飯");
}
}
創(chuàng)建代理類:
/**
* 代理主題角色 - 餐廳
*/
class ProxyCglib implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz){
//設(shè)置需要創(chuàng)建子類的類
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通過字節(jié)碼技術(shù)動態(tài)創(chuàng)建子類實例
return enhancer.create();
}
//實現(xiàn)MethodInterceptor接口方法
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("餐廳工作人員做飯......");
//通過代理類調(diào)用父類中的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("餐廳工作人員刷碗......");
return result;
}
}
測試類:
/**
* 測試類
* @author tianxiaopeng@hxy
* @date 2023/10/11 11:51 AM
*/
public class CglibTest {
public static void main(String[] args) {
ProxyCglib proxy = new ProxyCglib();
//通過生成子類的方式創(chuàng)建代理類
YourSelf proxyImp = (YourSelf)proxy.getProxy(YourSelf.class);
proxyImp.eat();
}
}
結(jié)果:
餐廳工作人員做飯......
自己吃飯
餐廳工作人員刷碗......
(2)CGLIB動態(tài)代理實現(xiàn)分析
CGLib動態(tài)代理采用了FastClass機制,其分別為代理類和被代理類各生成一個FastClass,這個FastClass類會為代理類或被代理類的方法分配一個 index(int類型)。這個index當(dāng)做一個入?yún)ⅲ現(xiàn)astClass 就可以直接定位要調(diào)用的方法直接進行調(diào)用,這樣省去了反射調(diào)用,所以調(diào)用效率比 JDK 動態(tài)代理通過反射調(diào)用更高。
但是我們看上面的源碼也可以明顯看到,JDK動態(tài)代理只生成一個文件,而CGLIB生成了三個文件,所以生成代理對象的過程會更復(fù)雜。
兩者區(qū)別
- JDK代理只能對實現(xiàn)接口的類生成代理;CGLib是針對類實現(xiàn)代理,對指定的類生成一個子類,并覆蓋其中的方法,這種通過繼承類的實現(xiàn)方式,不能代理final修飾的類。
- JDK代理使用的是反射機制實現(xiàn)aop的動態(tài)代理,CGLib代理使用字節(jié)碼處理框架ASM,通過修改字節(jié)碼生成子類。
- JDK動態(tài)代理機制是委托機制,具體說動態(tài)實現(xiàn)接口類,在動態(tài)生成的實現(xiàn)類里面委托hanlder去調(diào)用原始實現(xiàn)類方法,CGLib則使用的繼承機制,具體說被代理類和代理類是繼承關(guān)系,所以代理類是可以賦值給被代理類的,如果被代理類有接口,那么代理類也可以賦值給接口。