《Proxy系列專題》:代理模式(靜態、JDK、CGLib)

堅持到底gl發表於2021-02-07

《Proxy系列專題》:代理模式(靜態、JDK、CGLib)使用

  現象:在如今網際網路時代,專案的複雜度不斷的提升,有些場景下需要一定的設計優化來支撐業務的擴充套件,如為了不改動原始類,但需要對其做相應事件擴充套件,例如:日誌,事物,功能增強等。

  思想:想辦法用一個B類代表另一個A類的功能,不改變其A類本質。

  結果:代理模式出現,這只是一個思想,實現的方式有很多種,如:靜態代理、JDK動態代理、CGLib動態代理 等等其他的。


 使用方式

  本文主要寫代理模式主要的幾種程式碼的使用,其原理會接下來的文章種輸出。

  主要的代理方式

  1. 靜態代理(類似工廠)
  2. 動態代理
    • JDK動態代理(修改位元組碼JVM + 介面 + 反射)
    • CGLib動態代理(修改位元組碼ASM + 繼承 + 呼叫父類super)

 靜態代理

  靜態代理主要過程(整了一個外套--代理類)

  1. 在代理類中實現目標類相同的介面,並設定介面型別變數屬性
  2. 將目標類例項作為引數傳入賦值,如構造器方式,set方式等等
  3. 呼叫時其實是代理類的方法呼叫了相同目標類的方法,中轉一下,在呼叫目標類方法的前後做一些處理

  優點:代理類在編譯期生成,效率高。

  缺點:代理類會很多,後期維護複雜。

程式碼邏輯

  介面類UserService

public interface UserService {
    String getUserName(String username);
}

  介面實現類UserServiceImpl

public class UserServiceImpl implements UserService{

    @Override
    public String getUserName(String username) {
        System.err.println(username);
        return username;
    }
}

  代理類UserStaticProxy

public class UserStaticProxy implements UserService{

    private UserService userService;

    public UserStaticProxy(UserService userService) {
        this.userService = userService;
    }

    @Override
    public String getUserName(String username) {
        System.err.println("開始------");
        String userName = userService.getUserName(username);
        System.err.println("結束------");
        return userName;
    }
}

  測試類StaticProxyTest

public class StaticProxyTest {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        userService.getUserName("test");
        System.err.println("_______________________________________________");
        UserStaticProxy userStaticProxy = new UserStaticProxy(new UserServiceImpl());
        userStaticProxy.getUserName("lisi");
    }
}

 


 JDK動態代理

問題:為什麼會出現動態代理呢,靜態代理哪裡不好了

回答:若是又出現一個型別需要代理,需要再次寫一個代理類去維護包裝,或者型別中新增一個方法功能代理類也需要跟著維護,複雜過於耦合。所以就有想法如何解決這樣的尷尬困境。

  這時候出現了JDK動態代理,完美解決了新增型別和新增方法的一些尷尬問題。主要將靜態代理中代理類涉及到目標類有關的以其他種方式體現,變得可動態。

  主要流程:

  1. 從靜態代理中對目標類方法處理事件,進行了封裝,變成了InvocationHandler呼叫處理器作為引數形式傳遞,可以代理工具類內部實現,也可呼叫邏輯內部匿名實現
  2. 傳入目標類例項,進行相應的處理(提取有效資訊進行相應的位元組碼封裝)
  3. 代理工具類生成目標類的代理類,此時代理類例項在記憶體中,其內部與靜態代理很相似。

程式碼邏輯

  介面類SanGuoService

public interface SanGuoService {
    void warfare(String username);
}

  目標類 LiuBeiServiceImpl 實現 SanGuoService

public class LiuBeiServiceImpl implements SanGuoService{
    @Override
    public void warfare(String username) {
        System.err.println("LiuBei-warfare:"+username);
    }
}

  代理工具 JDKSimpleDynamicProxy 可實現 InvocationHandler 呼叫處理器,也可以邏輯內部匿名實現當作引數傳遞

  主要為了生成代理類

public class JDKSimpleDynamicProxy implements InvocationHandler {

    private Object object;
    //建立代理類例項
    public <T> T getNewProxy(T t){
        object = t;
        return (T) Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.err.println("------開始----");
        Object invoke = method.invoke(object, args);
        System.err.println("------結束----");
        return invoke;
    }
}

  JDK動態代理測試類 JDKSimpleDynamicProxyTest

public class JDKSimpleDynamicProxyTest {
    public static void main(String[] args) {
        JDKSimpleDynamicProxy jdkSimpleDynamicProxy = new JDKSimpleDynamicProxy();
        LiuBeiServiceImpl liuBeiService = new LiuBeiServiceImpl();
        SanGuoService sanGuoService = jdkSimpleDynamicProxy.getNewProxy(liuBeiService);
        sanGuoService.warfare("劉備");
    }
}

 


 

CGLib動態代理  

問題:有了JDK動態代理那麼方便好用高擴充套件,為什麼又要搞出一個CGLib動態代理呢。

回答:當一個型別沒有介面,則不能使用JDK動態代理了,除非給硬塞一個介面,很滑稽,那怎麼辦呢!所有設計了一個可以對目標類直接代理的方式,直接繼承,CGLib動態代理。

  其實CGLib和JDK動態代理使用流程上還是很相似的,只是內部邏輯實現不一樣,若封裝的好是看不出來的。

  主要流程:

  1. 首先明確需要對目標類方法處理要做的事,構造出MethodInterceptor實現,可以代理工具類內部實現,也可以外部業務邏輯自行實現。
  2. 將目標類例項傳入代理工具類中,進行相應引數拼裝,底層內部根據引數資訊邏輯拼裝位元組碼。
  3. 代理工具類生成目標類的代理類,此時代理類例項在記憶體中,代理類內部邏輯是以super形式呼叫父類方法

程式碼邏輯

  目標類:XiangYuServiceImpl 沒有介面類

public class XiangYuServiceImpl {

    public void warfare(String username) {
        System.err.println("XiangYu-warfare:"+username);
    }
}

  代理工具類:CGLibDynamicProxy  這類的MethodInterceptor上面說過可以這裡實現,也可以業務邏輯自己實現引數傳入,因為內部只需要一個引數例項

public class CGLibDynamicProxy implements MethodInterceptor {

    public <T>  T create(Class<T> classN){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(classN);
        enhancer.setCallback(this);
        return (T)enhancer.create();
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.err.println(proxy.getClass().getName());
        System.err.println("1111111");
        Object invoke = methodProxy.invokeSuper(proxy, args);
        System.err.println("22222222");
        return invoke;
    }
}

  測試類:CGLibDynamicProxyTest

public class CGLibDynamicProxyTest {
    public static void main(String[] args) {
        CGLibDynamicProxy cgLibDynamicProxy = new CGLibDynamicProxy();
        XiangYuServiceImpl xiangYuService = cgLibDynamicProxy.create(XiangYuServiceImpl.class);
        xiangYuService.warfare("guanglin");
    }
}

代理模式使用總結

  其實這三種代理模式都是想盡辦法建立一個代理類呼叫目標類來實現代理。

  動態代理的應用:Spring AOP、RPC呼叫、Hibernate的懶載入 等待

三種方式的區別
代理模式 代理類建立時機 建立方式 代理類行為 原理 呼叫處理器 效率方面

效能方面

靜態代理 編譯時期 手動建立 實現介面 直接呼叫目標類 高效  高效
JDK動態代理 執行時 基於JVM對位元組碼的操作實現 實現介面 反射呼叫 InvocationHandler

JDK1.6/1.7上的對比 

  • 類的建立速度:JDK快於CGLIB
  • 執行速度:JDK慢於CGLIB,大概慢2倍的關係

JDK1.8上的對比

  • 類的建立速度:JDK快於CGLIB
  • 執行速度:JDK快於CGLIB,經過努力,JDK1.8作了效能上的優化,速度明顯比1.7提升了很多 
CGLib動態代理 執行時 基於ASM對位元組碼的操作 繼承目標類 super呼叫父類 MethodInterceptor

相關文章