1. 程式人生 > 實用技巧 >靜態代理,jdk動態代理以及Cglib區別

靜態代理,jdk動態代理以及Cglib區別

眾所周知,Spring AOP中涉及到了動態代理模式,那麼有動態代理相應的就會有靜態代理。那麼動態代理分為哪幾種,相對應的區別又是什麼呢?

首先什麼是代理?

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

靜態代理:代理的是程式設計師已經建立好的類,也就是說當前僅有一個物件能被成功代理。上程式碼看下

首先是一個需要代理的介面類

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

public interface UserAction {
void eat();
void run();
}

接下來是該類的實現類,較為簡單的實現方式,僅僅列印內容而已。

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

介面已經實現完成,剩下的即是代理物件了。上述的過程中靜態代理和JDK的動態代理還沒有區別。區別在於下面

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

從上述程式碼可以看到,我們實現了介面類並定義了一個新的類 UserActionStaticProxy 。然後定義了他的有參構造方法,將介面類傳入即可。方法重寫的同時加入了方法的監控。

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

在呼叫的時候,我們可以看到傳入了UserActionImpl類去轉換為UserAction類(向下轉型),而後可以直接呼叫他的方法即可。執行上述方法以後即執行了 Proxy 類下的eat方法。甚至於我們可以在 Proxy 類下的eat方法呼叫一次 this.run() 方法,可以自己拼接為自己所想要的方式,有點和策略模式靠近了。(下圖是在eat方法中加入了this.run()方法)

從靜態代理模式中可以看出來,如果我們需要代理多個類的話,那麼就需要新建對應的介面實現類(Imp.class)和對應的代理類(Proxy,class)。所以實現起來會比較繁瑣,因此就應運而生出了動態代理。

動態代理和靜態代理的本質區別其實不是很大,都需要介面以及介面的實現類,動態代理解決了需要重複新增大量的單體代理檔案,而是把所有的物件都在一個地方進行了代理,也就是涉及到了JAVA中的反射機制。

可以看下動態代理的程式碼:

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("動態代理" + method.getName() + "開始");
Object ret = method.invoke(object,args);
System.out.println("動態代理" + method.getName() + "結束");
return ret;
}
}

可以看到,動態代理的時候,我們將物件替換成了所有物件的父類------Object類,在代理類的同時,我們通過反射Proxy.newProxyInstance 獲取了該類的物件。而後使用了java對應的invoke方法去執行被代理類的方法。

對應的執行的主方法:

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

執行的結果:

因此可以看到,靜態代理和動態代理的區在於代理類的區別,靜態代理在程式碼擴容時,每增加一個介面類需要代理,那麼就需要新增一個對應的代理類。而動態代理的好處在於需要新增代理介面時,不需要新增代理類,可以直接通過反射的方式呼叫被代理類。從上述就可以看出,代理的好處就是對方法的增強,可以在方法的前後進行一系列的操作,比如列印日誌,驗證許可權,方法之後可以統一返回格式,統一異常捕獲等等......(其實也就是AOP能做的事情)。所以動態代理相比於靜態代理最本質的區別就在於我們需要對一個新的介面類代理時,不需要再去增加繁瑣的代理類了。

前文提到,動態代理又分為JDK的動態代理以及CGLIB的動態代理。JDK的動態代理是依據的JAVA強大的反射機制。而CGLIB動態代理是利用asm開源包,對代理物件類的class檔案載入進來,通過修改其位元組碼生成子類來處理,也就是說生成被代理類的一個子類將其方法覆蓋,以達到代理的目的。

AOP是會自動切換這兩種動態代理的型別的,具體的區別如下:

1、如果目標物件實現了介面,預設情況下會採用JDK的動態代理實現AOP

2、如果目標物件實現了介面,可以強制使用CGLIB實現AOP

3、如果目標物件沒有實現了介面,必須採用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換

這裡借用別人的CGLIB程式碼來看下具體的區別

public class UserAction {
public void eat(){
System.out.println("CGLIB動態代理吃飯");
}
}
public class CglibProxy implements MethodInterceptor {
private Object target;//需要代理的目標物件
//重寫攔截方法
@Override
public Object intercept(Object obj, Method method, Object[] arr, MethodProxy proxy) throws Throwable {
System.out.println("Cglib動態代理,監聽開始!");
Object invoke = method.invoke(target, arr);//方法執行,引數:target 目標物件 arr引數陣列
System.out.println("Cglib動態代理,監聽結束!");
return invoke;
}
//定義獲取代理物件方法
public Object getCglibProxy(Object objectTarget){
//為目標物件target賦值

this.target = objectTarget;
Enhancer enhancer = new Enhancer();
//設定父類,因為Cglib是針對指定的類生成一個子類,所以需要指定父類

enhancer.setSuperclass(objectTarget.getClass());
enhancer.setCallback(this);// 設定回撥

Object result = enhancer.create();//建立並返回代理物件

return result;
}
public static void main(String[] args) {
CglibProxy cglib = new CglibProxy();//例項化CglibProxy物件

UserAction user =  (UserAction) cglib.getCglibProxy(new UserAction());//獲取代理物件

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


}
}

程式碼本質上其實和JDK的動態代理的區別並不是很大,而JDK的動態代理是基於介面的,必須要對應介面的實現類才可以實現JDK的動態代理,而CGLIB彌補了這方面的缺點,CGLIB是基於類的繼承關係,因此在沒有介面的實現下我們可以使用CGLIB去實現動態代理。

推薦閱讀

為什麼阿里巴巴的程式設計師成長速度這麼快

納尼?SpringCloud要被淘汰了?

《飛馬計劃》到底是什麼?可以讓數萬程式設計師為之著迷

一年半開發經驗拿多少錢合適?

看完三件事

如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:

點贊,轉發,有你們的 『點贊和評論』,才是我創造的動力。

關注公眾號 『 Java鬥帝 』,不定期分享原創知識。

同時可以期待後續文章ing