靜態(tài)代理,jdk動態(tài)代理以及Cglib區(qū)別

眾所周知,Spring AOP中涉及到了動態(tài)代理模式,那么有動態(tài)代理相應(yīng)的就會有靜態(tài)代理。那么動態(tài)代理分為哪幾種,相對應(yīng)的區(qū)別又是什么呢?

首先什么是代理?

找一個東西或者一個人去幫你做事,比如常說的中介就是一個代理,各大經(jīng)銷商的代理商等等。JAVA中的代理即是指將自己的事情委派給別人幫忙去完成。

靜態(tài)代理:代理的是程序員已經(jīng)創(chuàng)建好的類,也就是說當(dāng)前僅有一個對象能被成功代理。上代碼看下

首先是一個需要代理的接口類

該類描述了兩個方法,一個是eat(),一個是run();

public interface UserAction {
void eat();
void run();
}
image.gif

接下來是該類的實(shí)現(xiàn)類,較為簡單的實(shí)現(xiàn)方式,僅僅打印內(nèi)容而已。

public class UserActionImpl implements UserAction {
@Override
public void eat() {
System.out.println("吃飯");
}
@Override
public void run() {
System.out.println("跑步");
}
}
image.gif

接口已經(jīng)實(shí)現(xiàn)完成,剩下的即是代理對象了。上述的過程中靜態(tài)代理和JDK的動態(tài)代理還沒有區(qū)別。區(qū)別在于下面

public class UserActionStaticProxy implements UserAction{
private UserAction userAction;
public UserActionStaticProxy(UserAction userAction){
this.userAction = userAction;
}
@Override
public void eat() {
System.out.println("靜態(tài)代理eat方法開始");
userAction.eat();
System.out.println("靜態(tài)代理eat方法結(jié)束");
}
@Override
public void run() {
System.out.println("靜態(tài)代理run方法開始");
userAction.run();
System.out.println("靜態(tài)代理run方法結(jié)束");
}
}
image.gif

從上述代碼可以看到,我們實(shí)現(xiàn)了接口類并定義了一個新的類 UserActionStaticProxy 。然后定義了他的有參構(gòu)造方法,將接口類傳入即可。方法重寫的同時加入了方法的監(jiān)控。

public static void main(String[] args) {
UserAction userAction = new UserActionStaticProxy(new UserActionImpl());
userAction.eat();
}
image.gif

在調(diào)用的時候,我們可以看到傳入了UserActionImpl類去轉(zhuǎn)換為UserAction類(向下轉(zhuǎn)型),而后可以直接調(diào)用他的方法即可。運(yùn)行上述方法以后即執(zhí)行了 Proxy 類下的eat方法。甚至于我們可以在 Proxy 類下的eat方法調(diào)用一次 this.run() 方法,可以自己拼接為自己所想要的方式,有點(diǎn)和策略模式靠近了。(下圖是在eat方法中加入了this.run()方法)

image
image.gif

?

從靜態(tài)代理模式中可以看出來,如果我們需要代理多個類的話,那么就需要新建對應(yīng)的接口實(shí)現(xiàn)類(Imp.class)和對應(yīng)的代理類(Proxy,class)。所以實(shí)現(xiàn)起來會比較繁瑣,因此就應(yīng)運(yùn)而生出了動態(tài)代理。

動態(tài)代理和靜態(tài)代理的本質(zhì)區(qū)別其實(shí)不是很大,都需要接口以及接口的實(shí)現(xiàn)類,動態(tài)代理解決了需要重復(fù)新增大量的單體代理文件,而是把所有的對象都在一個地方進(jìn)行了代理,也就是涉及到了JAVA中的反射機(jī)制。

可以看下動態(tài)代理的代碼:

public class LogHandler implements InvocationHandler {
private Object object;
public Object newProxyInstance(Object object){
this.object = object;
return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
System.out.println("動態(tài)代理" + method.getName() + "開始");
Object ret = method.invoke(object,args);
System.out.println("動態(tài)代理" + method.getName() + "結(jié)束");
return ret;
}
}
image.gif

可以看到,動態(tài)代理的時候,我們將對象替換成了所有對象的父類------Object類,在代理類的同時,我們通過反射Proxy.newProxyInstance 獲取了該類的對象。而后使用了java對應(yīng)的invoke方法去執(zhí)行被代理類的方法。

對應(yīng)的執(zhí)行的主方法:

public static void main(String[] args) {
UserAction userAction1 = (UserAction)new LogHandler().newProxyInstance(new UserActionImpl());
userAction1.eat();
}
image.gif

執(zhí)行的結(jié)果:

image
image.gif

?

因此可以看到,靜態(tài)代理和動態(tài)代理的區(qū)在于代理類的區(qū)別,靜態(tài)代理在代碼擴(kuò)容時,每增加一個接口類需要代理,那么就需要新增一個對應(yīng)的代理類。而動態(tài)代理的好處在于需要新增代理接口時,不需要新增代理類,可以直接通過反射的方式調(diào)用被代理類。從上述就可以看出,代理的好處就是對方法的增強(qiáng),可以在方法的前后進(jìn)行一系列的操作,比如打印日志,驗(yàn)證權(quán)限,方法之后可以統(tǒng)一返回格式,統(tǒng)一異常捕獲等等......(其實(shí)也就是AOP能做的事情)。所以動態(tài)代理相比于靜態(tài)代理最本質(zhì)的區(qū)別就在于我們需要對一個新的接口類代理時,不需要再去增加繁瑣的代理類了。

前文提到,動態(tài)代理又分為JDK的動態(tài)代理以及CGLIB的動態(tài)代理。JDK的動態(tài)代理是依據(jù)的JAVA強(qiáng)大的反射機(jī)制。而CGLIB動態(tài)代理是利用asm開源包,對代理對象類的class文件加載進(jìn)來,通過修改其字節(jié)碼生成子類來處理,也就是說生成被代理類的一個子類將其方法覆蓋,以達(dá)到代理的目的。

AOP是會自動切換這兩種動態(tài)代理的類型的,具體的區(qū)別如下:

1、如果目標(biāo)對象實(shí)現(xiàn)了接口,默認(rèn)情況下會采用JDK的動態(tài)代理實(shí)現(xiàn)AOP

2、如果目標(biāo)對象實(shí)現(xiàn)了接口,可以強(qiáng)制使用CGLIB實(shí)現(xiàn)AOP

3、如果目標(biāo)對象沒有實(shí)現(xiàn)了接口,必須采用CGLIB庫,spring會自動在JDK動態(tài)代理和CGLIB之間轉(zhuǎn)換

這里借用別人的CGLIB代碼來看下具體的區(qū)別

public class UserAction {
public void eat(){
System.out.println("CGLIB動態(tài)代理吃飯");
}
}
public class CglibProxy implements MethodInterceptor {
private Object target;//需要代理的目標(biāo)對象
//重寫攔截方法
@Override
public Object intercept(Object obj, Method method, Object[] arr, MethodProxy proxy) throws Throwable {
System.out.println("Cglib動態(tài)代理,監(jiān)聽開始!");
Object invoke = method.invoke(target, arr);//方法執(zhí)行,參數(shù):target 目標(biāo)對象 arr參數(shù)數(shù)組
System.out.println("Cglib動態(tài)代理,監(jiān)聽結(jié)束!");
return invoke;
}
//定義獲取代理對象方法
public Object getCglibProxy(Object objectTarget){
//為目標(biāo)對象target賦值

this.target = objectTarget;
Enhancer enhancer = new Enhancer();
//設(shè)置父類,因?yàn)镃glib是針對指定的類生成一個子類,所以需要指定父類

enhancer.setSuperclass(objectTarget.getClass());
enhancer.setCallback(this);// 設(shè)置回調(diào)

Object result = enhancer.create();//創(chuàng)建并返回代理對象

return result;
}
public static void main(String[] args) {
CglibProxy cglib = new CglibProxy();//實(shí)例化CglibProxy對象

UserAction user =  (UserAction) cglib.getCglibProxy(new UserAction());//獲取代理對象

user.eat();//執(zhí)行方法

}
}
image.gif

代碼本質(zhì)上其實(shí)和JDK的動態(tài)代理的區(qū)別并不是很大,而JDK的動態(tài)代理是基于接口的,必須要對應(yīng)接口的實(shí)現(xiàn)類才可以實(shí)現(xiàn)JDK的動態(tài)代理,而CGLIB彌補(bǔ)了這方面的缺點(diǎn),CGLIB是基于類的繼承關(guān)系,因此在沒有接口的實(shí)現(xiàn)下我們可以使用CGLIB去實(shí)現(xiàn)動態(tài)代理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容