12.java設計模式之代理模式

基本介紹:

  • 代理模式(Proxy)為一個對象提供一個替身,以控制對這個對象的訪問。即通過代理對象訪問目標對象.這樣做的好處是:可以在目標對象實現的基礎上,增強額外的功能操作,即擴展目標對象的功能,想在訪問一個類時做一些控制
  • 代理的對象可以是遠程對象、創建開銷大的對象或需要安全控制的對象,代理模式有不同的形式, 主要有三種靜態代理(需要實際的類文件)、動態代理 (JDK 代理、接口代理)和Cglib理代理(可以在內存動態的創建對象,而不需要實現接口, 他是屬於動態代理的範疇)

靜態代理:

  • 靜態代理在使用時,需要定義接口或者父類,被代理對象(即目標對象)與代理對象一起實現相同的接口或者是繼承相同父類

  • 代理對象類也需要創建一個文件

  • UMl類圖

  • 代碼實現

    • public interface ITeacherDao {
      
         // 接口父類
         void teach();
      
      }
      
      // 真實對象實現類
      class TeacherDao implements ITeacherDao {
      
         @Override
         public void teach() {
             System.out.println("目標對象teach...");
         }
      
      }
      
      // 代理對象實現類
      class TeacherDaoProxy implements ITeacherDao {
      
         // 聚合真實對象(本次使用的接口)
         private ITeacherDao teacherDao;
      
         public TeacherDaoProxy(ITeacherDao teacherDao) {
             this.teacherDao = teacherDao;
         }
      
         @Override
         public void teach() {
             // 在執行真實目標方法前後可做一些額外的事情,對真實目標方法進行強
             System.out.println("靜態代理前置增強");
             // 執行真實對象的目標方法
             teacherDao.teach();
             System.out.println("靜態代理後置增強");
         }
      }
      
      
    • public class Client {
         public static void main(String[] args) {
             // 創建代理對象
             TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(new TeacherDao());
             // 執行增強的目標方法
             teacherDaoProxy.teach();
         }
      }
      
  • 優點:在不修改目標對象的功能前提下, 能通過代理對象對目標功能擴展

  • 缺點:因為代理對象需要與目標對象實現一樣的接口,所以會有很多代理類,一旦接口增加方法,目標對象與代理對象都要維護

jdk動態代理:

  • 代理對象,不需要實現接口,但是目標對象要實現接口,否則不能用動態代理

  • 代理對象的生成,是利用JDK的API,動態的在內存中構建代理對象

  • 動態代理也叫做:JDK代理、接口代理

  • jdk動態代理生成的代理對象和真實對象時兄弟關係,都有共同的父接口,必須得有接口

  • UMl類圖

  • 代碼實現

    • public class ProxyFactory {
      
         private Object object;
      
         public ProxyFactory(Object object) {
             this.object = object;
         }
      
         public Object getProxyInstance() {
             /**直接使用jdk的Proxy類的newProxyInstance即可 注意:生成的代理對象和真實對象是兄弟關係
              * 參數1:目標對象的類加載器
              * 參數2:目標對象的接口數組(為了方便知道目標對象中有哪些方法,代理對象可以調用)
              * 參數3:事件處理,代理對象調用方法時,所調用的邏輯(直接使用匿名內部類)
              */
             return Proxy.newProxyInstance(
                     this.object.getClass().getClassLoader(),
                     this.object.getClass().getInterfaces(),
                     new InvocationHandler() {
                 @Override
                 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                     /**
                      * 代理對象調用方法時,所調用的邏輯
                      * 參數1:代理對象 參數2:所調用的真實方法 參數3:調用真實方法的參數列表
                      * 在執行真實目標方法前後可做一些額外的事情,對真實目標方法進行強
                      * 通過反射調用真實對象的真實方法
                      */
                     System.out.println("jdk動態代理前置增強");
                     // 執行真實對象的目標方法
                     Object invoke = method.invoke(object, args);
                     System.out.println("jdk動態代理後置增強");
                     return invoke;
                 }
             });
         }
      }
      
    • public class Client {
         public static void main(String[] args) {
             // 創建代理工廠
             ProxyFactory proxyFactory = new ProxyFactory(new TeacherDao());
             // 獲取代理對象 調用增強方法
             ITeacherDao teacherDao = (ITeacherDao) proxyFactory.getProxyInstance();
             teacherDao.teach();
             System.out.println(teacherDao.getClass()); // 在內存中動態生成的代理對象 class com.sun.proxy.$Proxy0
         }
      }
      
      

cglib動態代理:

  • 靜態代理和JDK代理模式都要求目標對象是實現一個接口,但是有時候目標對象只是一個單獨的對象,並沒有實現任何的接口,這個時候可使用目標對象子類來實現代理-這就是Cglib代理

  • Cglib代理也叫作子類代理,它是在內存中構建一個子類對象從而實現對目標對象功能擴展

  • Cglib是一個強大的高性能的代碼生成包,它可以在運行期擴展java類與實現java接口.它廣泛的被許多AOP的框架使用,例如 SpringAOP,實現方法攔截

  • 在AOP編程中如何選擇代理模式:目標對象需要實現接口,用JDK代理目標對象,不需要實現接口,用Cglib代理

  • Cglib包的底層是通過使用位元組碼處理框架ASM來轉換位元組碼並生成新的類

  • 注意問題

    • 在內存中動態構建子類,注意代理的類不能為 final,否則報錯 java.lang.IllegalArgumentException
    • 目標對象的方法如果為 final/static,那麼就不會被攔截,即不會執行目標對象額外的業務方法
  • UML類圖

  • 代碼實現

    • <!--pom 中導入cglib依賴-->
      <dependency>
         <groupId>cglib</groupId>
         <artifactId>cglib</artifactId>
         <version>2.1_3</version>
      </dependency>
      
    • public class ProxyFactory implements MethodInterceptor {
      
         private Object object;
      
         public ProxyFactory(Object object) {
             this.object = object;
         }
      
         public Object getProxyInstance() {
             // 創建工具類 或使用靜態方法 Enhancer.create() 其中所需要的回調對象直接使用匿名內部類 new MethodInterceptor
             Enhancer enhancer = new Enhancer();
             // 設置父類 注意所生成的代理對象和真實對象是父子關係,所以要求父類不能被final修飾 所攔截的方法不能是final和static
             enhancer.setSuperclass(this.object.getClass());
             // 設置回調 this代表本身,代理對象執行方法時會調用MethodInterceptor接口的intercept方法,而本類實現了該接口,所以直接設置this即可
             enhancer.setCallback(this);
             // 創建代理對象
             return enhancer.create();
         }
      
         @Override
         public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
             /**
              * 這個方法和jdk動態代理中的new InvocationHandler 中的 invoke方法作用一樣 起攔截作用
              * 參數1:代理對象 參數2:執行的真實對象的方法 參數3:真實方法的參數列表 參數4:真實方法的代理對象
              * 在執行真實目標方法前後可做一些額外的事情,對真實目標方法進行強
              * 通過反射調用真實對象的真實方法
              */
             System.out.println("cglib動態代理前置增強");
             // 執行真實對象的目標方法
             Object invoke = method.invoke(object, objects);
             System.out.println("cglib動態代理後置增強");
             return invoke;
         }
      }
      
      
    • public class Client {
         public static void main(String[] args) {
             // 創建代理工廠
             ProxyFactory proxyFactory = new ProxyFactory(new TeacherDao());
             // 獲取代理對象 調用增強方法 為什麼debug時會調用多次?
             ITeacherDao teacherDao = (ITeacherDao) proxyFactory.getProxyInstance();
             teacherDao.teach();
             System.out.println(teacherDao.getClass()); // 在內存中動態生成的代理對象 TeacherDao$$EnhancerByCGLIB$$a5278286
         }
      }
      

其他代理:

  • 防火牆代理:內網通過代理穿透防火牆,實現對公網的訪問
  • 緩存代理:當請求圖片文件等資源時,先到緩存代理取,如果取到資源則ok,如果取不到資源,再到公網或者數據庫取,然後緩存
  • 遠程代理:遠程對象的本地代表,通過它可以把遠程對象當本地對象來調用,遠程代理通過網絡和真正的遠程對象溝通信息
  • 同步代理:主要使用在多線程編程中,完成多線程間同步工作(通過代理對象和真實對象和進行交互來抱枕線程安全)

注意事項:

  • 缺點:由於在客戶端和真實主題之間增加了代理對象,因此有些類型的代理模式可能會造成請求的處理速度變慢。 實現代理模式需要額外的工作,有些代理模式的實現非常複雜
  • 和適配器模式的區別:適配器模式主要改變所考慮對象的接口,而代理模式不能改變所代理類的接口
  • 和裝飾器模式的區別:裝飾器模式為了增強功能,而代理模式是為了加以控制