1. 程式人生 > >設計模式之代理模式(全面講解)

設計模式之代理模式(全面講解)

      設計模式是一套被反覆使用,多數人知曉,經過分類編目的,程式碼設計的總結,也可以說是前人的智慧結晶。學習設計模式能讓我們對一些應用場景使用相同的套路達到很好的效果,我會不定時更新一些自己對設計模式的理解的文章,從定義,實現,應用場景來說說設計模式,今天我要說的物件是代理模式

  一:定義   

    代理模式:給某一個物件提供一個代理或佔位符,並由代理物件來控制對原物件的訪問。代理模式是常用的結構型設計模式之一,當無法直接訪問某個物件或不適合直接訪問某個物件時可以通過一個代理物件來間接訪問,代理(Proxy)是一種設計模式,提供了對目標物件另外的訪問方式;即通過代理物件訪問目標物件.這樣做的好處是:可以在目標物件實現的基礎上,增強額外的功能操作,即擴充套件目標物件的功能。這麼做也服務開閉原則(對擴充套件開放對修改關閉)的要求。代理模式的關鍵點是:代理物件會呼叫被代理物件,代理物件對被代理物件的功能做了擴充套件。

    打個比方,我們想要購買國外的某些商品,由於各種成本問題,直接去一趟國外不划算,於是我們找了代購幫我們購買,這裡的代購幫我們到國外的實體店完成了購買的動作,他就是實體店的代理,而實體店是被代理,而我們和代購直接的溝通交涉,相當於是對被代理功能的擴充套件,而這一步是在我們和代理直接完成,不會影響被代理的商店

 

  二:實現

    從實現上來說,代理模式可以分為靜態,動態代理和子類代理

    靜態代理:靜態代理在使用時,需要定義介面或者父類,被代理物件與代理物件一起實現相同的介面或者是繼承相同父類,所謂靜態也就是在程式執行前就已經存在代理類的位元組碼檔案,代理類和委託類的關係在執行前就確定了。下面展示程式碼實現

    //定義介面

    interface UserDao{

        void save();
    }
    //被代理類實現介面
    class UserService implements UserDao{
     @Override
     public void save() {
      System.out.println("這是需要被代理的儲存方法");
     }
    }

    //代理類代理相關的方法
    class UserDaoProxy implements UserDao{
      //在代理類中引用被代理類,這裡只是使用最簡單的方法

     private UserDao iUserDao;
    public UserDaoProxy(UserDao iUserDao){
    this.iUserDao=iUserDao;
    }
      //引用並擴充套件被代理類的方法
    @Override
    public void save() {
     System.out.println("這裡編寫需要擴充套件的內容");
    iUserDao.save();
    System.out.println("儲存成功或者失敗之後的操作");
  }

  //測試一下
public static void main(String[] df){

UserDaoProxy proxy=new UserDaoProxy(new UserService()); proxy.save();
}
}  
    動態代理:代理物件是動態生成的,不需要實現父類介面
      動態的生成一個物件,很自然的就想到反射。JDK中有動態生成代理的包(java.lang.reflect.Proxy),是專門為建立動態代理物件存在的,它的內部實現是使用了反射技術,JDK實現代理只需要使用newProxyInstance方法,但是該方法需要接收三個引數,完整的寫法是: static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )注意該方法是在Proxy類中是靜態方法,且接收的三個引數依次為:        ● ClassLoader loader,:指定當前目標物件使用類載入器,獲取載入器的方法是固定的       ● Class<?>[] interfaces,:目標物件實現的介面的型別,使用泛型方式確認型別       ● InvocationHandler h:事件處理,執行目標物件的方法時,會觸發事件處理器的方法,會把當前執行目標物件的方法作為引數傳入。
    這裡使用一個工廠類建立動態代理物件來代替上面的代理物件,其他方法不變
    class ProxyFactory{      public static Object getProxyInstance(Object target){       return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {       @Override        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {          System.out.println("這是代理物件擴充套件的內容");         Object object=method.invoke(target,args);        return object;       }        });     }
      //測試一下
    public static void  main(String[] df){

UserDao object=(UserDao)ProxyFactory.getProxyInstance(new UserService());
object.save();
}
    }
      子類代理:
          上面的靜態代理和動態代理模式都是要求目標物件是實現一個介面的目標物件,但是有時候目標物件只是一個單獨的物件,並沒有實現任何的介面,這個時候就可以使用以目標物件子類的方式類實現代理,這種方法就叫做:Cglib代理

        Cglib代理,也叫作子類代理,它是在記憶體中構建一個子類物件從而實現對目標物件功能的擴充套件.

        ● JDK的動態代理有一個限制,就是使用動態代理的物件必須實現一個或多個介面,如果想代理沒有實現介面的類,就可以使用Cglib實現.
        ● Cglib是一個強大的高效能的程式碼生成包,它可以在執行期擴充套件java類與實現java介面.它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop,為他們提供方法的interception(攔截)
        ● Cglib包的底層是通過使用一個小而塊的位元組碼處理框架ASM來轉換位元組碼並生成新的類.不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class檔案的格式和指令集都很熟悉.
          Cglib子類代理實現方法:
        1.需要引入cglib的jar檔案,但是Spring的核心包中已經包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.
        2.引入功能包後,就可以在記憶體中動態構建子類
        3.代理的類不能為final,否則報錯
        4.目標物件的方法如果為final/static,那麼就不會被攔截,即不會執行目標物件額外的業務方法.

         //被代理物件,沒有實現任何介面
      class UserDao {

              public void save() {
                System.out.println("----已經儲存資料!----");
              }
      }

      class ProxyFactory implements MethodInterceptor{

          //給目標物件建立一個代理物件
          public Object getProxyInstance(Object target){
            //1.工具類
            Enhancer en = new Enhancer();
            //2.設定父類
            en.setSuperclass(target.getClass());
            //3.設定回撥函式
            en.setCallback(this);
            //4.建立子類(代理物件)
            return en.create();
          }

        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
              System.out.println("這是需要擴充套件的內容...");

            //執行目標物件的方法
            Object returnValue = method.invoke(target, args);

              System.out.println("這是需要擴充套件的內容...");

            return returnValue;
        }

        //測試一下

        public static void main(String[] arg){  

          //被代理物件
          UserDao target = new UserDao();

          //代理物件
          UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();

          //執行代理物件的方法
          proxy.save();

        }
      }

 

  三:應用場景

    從應用場景來說,代理模式可以分為遠端代理,緩衝代理,智慧引用代理,虛擬代理,保護代理等等

    (1) 當客戶端物件需要訪問遠端主機中的物件時可以使用遠端代
    (2) 當需要用一個消耗資源較少的物件來代表一個消耗資源較多的物件,從而降低系縮短執行時間時可以使用虛擬代理,例如一個物件需要很長時間才能完成加
    (3) 當需要為某一個被頻繁訪問的操作結果提供一個臨時儲存空間,以供多個客戶端共享訪問這些結果時可以使用緩衝代理。通過使用緩衝代理,系統無須在客戶端每一次訪問時都重新執行操作,只需直接從臨時緩衝區獲取操作結果即可。
    (4) 當需要控制對一個物件的訪問,為不同使用者提供不同級別的訪問許可權時可以使用保護代理。
    (5) 當需要為一個物件的訪問(引用)提供一些額外的操作時可以使用智慧引用代理。