天天看點

設計模式~代理模式

學習代理模式内容:

★ 靜态代理、

★ 動态代理(JDK動态代理、CGLIB動态代理)、

★ 攔截器的原理和日志記錄

★ 代理總結

一、職責分離的例子---房屋租賃

1、重複

2、職責不分離

●【​

​陪着看房、陪着談價格、交鑰匙​

​​】----不應該交個房東來重複做,不是他關心的重點,作為房東他隻需要關心【​

​簽合同、收房租​

​】

----解決:把這部分重複非業務重點的代碼重構抽離給​

​第三者​

​----中介

● 抽離之後,中介也會重複了呀,那怎麼辦呢?

----沒事,這是人家的責任,人家就是靠這個賺錢的。

✿ ​

​用戶端Client----- 第三方(目的:增強Service的功能)-----服務端Service​

二、代理模式 [設計模式] ​​中介作用​​

✿ 1、代理模式:用戶端直接使用的都是​

​代理對象​

​​,并不知道真實對象是誰,此時代理對象可以在用戶端和真實對象之間起到​

​中介作用​

​。

(1)代理對象完全包含真實對象,用戶端使用的都是代理對象的方法,和真實對象沒有直接關系;

(2)代理模式的職責:把不是真實對象該做的事情從真實對象上撇開——職責清晰;

三、靜态代理

1、概念/原理:

在程式運作前就已經存在代理類的位元組碼檔案,代理對象和真實對象的關系在運作前就确定了。

2、靜态代理的實作過程:

■ 将功能封裝成一個接口,​

​代理類和真實類/委托類 都需要實作該接口​

​:

真實類/委托類 需要實作該接口,很好了解,才具有特定的功能。
代理類 需要實作該接口,是因為這樣子才知道需要為哪些功能做代理、做增強。
設計模式~代理模式

3、靜态代理的優缺點:

● 優點:職責分離、安全

1,業務類隻需要關注業務邏輯本身,保證了業務類的重用性。

2,把真實對象隐藏起來了,保護真實對象。

● 缺點:代碼臃腫(一個真實對象需要對應一個代理對象)、不符合開閉原則(不友善擴充和維護)。

----​

​一個代理類隻能服務某一個業務接口​

​。

1,代理對象的某個接口隻服務于某一種類型的對象,也就是說每一個真實對象都得建立一個代理對象。

2,如果需要代理的方法很多,則要為每一種方法都進行代理處理。

3,如果接口增加一個方法,除了所有實作類需要實作這個方法外,代理類也需要實作此方法

4、總結靜态代理:

1、靜态代理需要實作某個接口(才知道要代理、增強什麼功能)

2、靜态代理要包含真實對象(真實對象是靜态代理類的對象屬性)----内部bean,不暴露給外界

3、測試,通過接口對象進行測試,直接調用的是靜态代理對象(間接調用真實對象)

【動态代理在測試時,是通過動态代理類,先擷取到代理對象,直接調用的是動态代理對象(間接調用真實對象)】

  • 擷取到代理對象:jdk提供的Proxy的方法newProxyInstance

● 詳細的代碼:

/* 靜态代理類【需要實作接口,才知道需要為哪些功能做代理、做增強】*/
public class EmployeeServiceProxy implements IEmployeService{
  @Setter
  private IEmployeService target;//真實對象/委托對象
  @Setter
  private TransactionManager txManager;//事務管理器
  
  @Override
  public void save(Employee e) {
    txManager.open();
    try {
      target.save(e);
      txManager.commit();
    } catch (Exception e2) {
      txManager.rollback();
      e2.printStackTrace();
    }
  }
  
  @Override
  public void update(Employee e) {
    txManager.open();
    try {
      target.update(e);
      txManager.commit();
    } catch (Exception e2) {
      txManager.rollback();
      e2.printStackTrace();
    }
  }
}


/* 測試類 (先擷取代理對象)*/
@SpringJUnitConfig
public class App {
    
  @Autowired
  private IEmployeService service;

  @Test
  void testSave() throws Exception {
    Employee e = new Employee();
    e.setName("shangke");
    e.setAge(28);
        
    service.save(e);//調用接口對象【根據bean配置,實際調用的是靜态代理對象】
//         System.out.println(service);//接口對象的真實類型【根據bean配置,實際調用的是靜态代理對象】
//      System.out.println(service.getClass());
  }
}      

● 靜态代理bean對象的配置:

設計模式~代理模式

四、動态代理

1、學習動态動态代理之前的準備工作:位元組碼的動态加載

(1) 先了解一下java的編譯運作原理【java加載位元組碼原理】:

  • 編譯:将源檔案 .java 檔案,通過編譯器(javac 指令) 編譯成 位元組碼檔案 .class 檔案。【​

    ​編譯得到位元組碼檔案​

    ​】
  • 運作:通過​

    ​類加載器​

    ​(以二進制流形式)把位元組碼加載進JVM,通過java解析器(java 指令) 進行運作程式。【​

    ​jvm解析位元組碼檔案​

    ​】

(2) 如何動态建立一份位元組碼?(實作了在代碼中動态建立一個類的能力):

  • 通過java的編譯和運作原理,可以看到:在運作時期,是jvm通過位元組碼的二進制資訊來加載類的。

是以,當我們在運作時期,通過java編譯系統組織.class檔案的格式和結構,生成相應的二進制資料,然後再把這個二進制資料加載轉換成對應的類。

2、靜态代理和動态代理的(原理)差別:

■ 靜态代理:(經曆了​

​編譯​

​和運作)

​在程式運作前就已經存在代理類的位元組碼檔案​

​​(因為通過了編譯階段),​

​代理對象和真實對象的關系在運作前就确定了​

​(因為通過了編譯階段)。

■ 動态代理:(​

​隻經曆了運作​

​,咱通過某種手段得到的位元組碼【遵循位元組碼格式和結構】)

動态代理類是在程式運作期間由jvm通過反射等機制動态生成的,是以​

​不存在代理類的位元組碼檔案​

​​(因為沒有經曆編譯階段),​

​代理對象和真實對象的關系是在程式運作期間才确定的​

​。

□ 兩個原理相同點:

用戶端直接使用的都是​

​代理對象​

​​,并不知道真實對象是誰,此時代理對象可以在用戶端和真實對象之間起到​

​中介作用​

​。

3、動态代理分類:JDK動态代理、CGLIB動态代理

(1)JDK動态代理:

① 使用JDK的 ​

​Proxy的newProxyInstance​

​ 建立動态代理對象
//動态代理---擷取代理對象
  @SuppressWarnings("unchecked")
  public <T> T getProxyObject() {
    return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),//真實對象的類加載器
        target.getClass().getInterfaces(), //真實對象實作的接口
         this);//如何做事務增強的對象【增強器】
  }  //動态代理---擷取代理對象
  @SuppressWarnings("unchecked")
  public <T> T getProxyObject() {
    return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),//真實對象的類加載器
        target.getClass().getInterfaces(), //真實對象實作的接口
         this);//如何做事務增強的對象【增強器】
  }      

● 詳細的代碼:

/* 動态代理:事務的增強操作 */
public class TransactionMangagerAdvice implements InvocationHandler{
  @Setter
  private Object target;//真實對象/委托對象
  @Setter
  private TransactionManager txManager;//事務增強器
  
  //動态代理---擷取代理對象
  @SuppressWarnings("unchecked")
  public <T> T getProxyObject() {
    return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),//真實對象的類加載器
        target.getClass().getInterfaces(), //真實對象實作的接口
         this);//如何做事務增強的對象【增強器】
  }

    
  //如何為真實對象的方法做增強具體操作
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object ret = null;
    txManager.open();
    try {
            //======================================================
      ret = method.invoke(target, args);//調用真實對象的方法
            //======================================================
      txManager.commit();
    } catch (Exception e) {
      e.printStackTrace();
      txManager.rollback();
    }
    return ret;
  }
}


/* 測試類 (先擷取代理對象)*/
@SpringJUnitConfig
public class App {
    
  @Autowired
  private TransactionMangagerAdvice advice;

  @Test
  void testSave() throws Exception {
    Employee e = new Employee();
    e.setName("shang");
    e.setAge(10);  
    e.setId(2L);
    
    //擷取代理對象
    IEmployeService proxy = advice.getProxyObject();
        //調用代理對象的儲存操作
    proxy.save(e);
        
//    System.out.println(proxy);//TransactionMangagerAdvice對象的真實類型是代理對象
//    System.out.println(proxy.getClass());//對象的真實類型
  }
}      

☺(2) 動态代理原理:

  • 原理和靜态代理差不多【用戶端直接使用的都是​

    ​代理對象​

    ​,并不知道真實對象是誰,此時代理對象可以在用戶端和真實對象之間起到​

    ​中介作用​

    ​。通過代理對象間接的調用真實對象的方法】
  • 隻不過,​

    ​動态代理的代理類​

    ​,不是由我們所建立,是我們生成位元組碼對應格式和結構的二進制資料加載進虛拟機,​

    ​動态生成的​

    ​。

● 詳細的代碼[通過 DynamicProxyClassGenerator 生成動态代理的位元組碼,再通過反編譯工具檢視。]:

public class DynamicProxyClassGenerator {
  public static void main(String[] args) throws Exception {
    generateClassFile(EmployeeServiceImpl.class, "EmployeeServiceProxy");
  }

  public static void generateClassFile(Class targetClass, String proxyName)throws Exception {
    //根據類資訊和提供的代理類名稱,生成位元組碼
    byte[] classFile =
      ProxyGenerator.generateProxyClass(proxyName, targetClass.getInterfaces());
    String path = targetClass.getResource(".").getPath();
    System.out.println(path);
    FileOutputStream out = null;
    //保留到硬碟中
    out = new FileOutputStream(path + proxyName + ".class");
    out.write(classFile);
    out.close();
  }
}      

(3) CGLIB動态代理:

  • 第三方,需要拷貝jar包【spring-frame架構已經內建了cglib動态代理】

原理:繼承

設計模式~代理模式

● 詳細的代碼:

/* cglib動态代理:事務的增強操作 [和jdk的差別在建立代理對象方式上] */
//動态代理---擷取代理對象
  @SuppressWarnings("unchecked")
  public <T> T getProxyObject() {
        Enhancer enhancer = new Enhancer();
        return (T)enhancer.create(target.getClass(), this);//建立代理對象
    //enhancer.setSuperclass(target.getClass());//将繼承哪一個類,去做增強
    //enhancer.setCallback(this);//設定增強對象【增強器】  
    //return (T)enhancer.create();//建立代理對象
  }      

(4)JDK動态代理和CGLIB動态代理的差別:

■ JDK動态代理:​

​要求 真實對象 必須要實作接口​

​。
■ CGLIB動态代理:可以針對沒有接口.
設計模式~代理模式

☺ 五、代理的總結:

1、代理原理圖:

設計模式~代理模式

☺ 2、​

​代理的目标/作用:為了給目标對象(真實對象)的方法做功能的增強​

​。

☺ 3、動态代理原理:

  • 原理和靜态代理差不多【用戶端直接使用的都是​

    ​代理對象​

    ​,并不知道真實對象是誰,此時代理對象可以在用戶端和真實對象之間起到​

    ​中介作用​

    ​。通過代理對象間接的調用真實對象的方法】
  • 隻不過,​

    ​動态代理的代理類​

    ​,不是由我們所建立,是我們生成位元組碼對應格式和結構的二進制資料加載進虛拟機,​

    ​動态生成的​

    ​。

4、JDK 動态代理 和 CGLIB 動态代理的總結:​

​有接口-使用jdk,沒有接口-使用cglib​

(1) JDK 動态代理:

① JAVA 動态代理是使用 ​

​java.lang.reflect 包​

​​中的 ​

​Proxy 類與 InvocationHandler 接口​

​這兩個來完成的。

② 要使用 JDK 動态代理,​

​委托類(真實類)必須要定義接口​

​。

③ ​

​JDK 動态代理将會攔截所有 pubic 的方法​

​(因為隻能調用接口中定義的方法),這樣即使在接口中增加 了新的方法,不用修改代碼也會被攔截。

④ 動态代理的最小機關是類(​

​所有類中的方法都會被處理​

​​),如果隻想攔截一部分方法,可以在 invoke 方法 中對要執行的方法名​

​進行判斷​

​ [判斷内容可以放到配置檔案,友善後續修改和維護~]

(2) CGLIB 動态代理:

① ​

​CGLIB 可以生成委托類的子類,并重寫父類非 final 修飾符的方法​

​。

② 要求類不能是 final 的,要攔截的方法要是非 final、非 static、非 private 的。

③ 動态代理的最小機關是類(所有類中的方法都會被處理);

5、性能和選擇 [​

​有接口-使用jdk,沒有接口-使用cglib​

​ + 性能要求有要求-Javassit]

  • JDK 動态代理是基于實作接口的,CGLIB 和 Javassit 是基于繼承委托類的。
  • 從性能上考慮:Javassit > CGLIB > JDK Struts2 的攔截器和 Hibernate 延遲加載對象,采用的是 Javassit 的方式.
  • 對接口建立代理優于對類建立代理,因為會産生更加松耦合的系統,也更​

    ​符合面向接口程式設計規範​

    ​。
  • 若委托對象實作了幹接口,優先選用 JDK 動态代理。 若委托對象沒有實作任何接口,使用 Javassit 和 CGLIB 動态代

☺ 6、動态代理的應用:過濾器、攔截器、日志記錄

1、過濾器Filter

2、攔截器Interceptor

  • 過濾器和攔截器差不多,隻是過濾器是針對與web領域的概念,隻能針對與請求和響應做增強,離不開servlet-api.jar;而攔截器是對于整個java領域的概念,不僅可以應用到web層,還可以應用到service層。

3、日志記錄log