1. 程式人生 > >java設計模式--代理模式(靜態代理和動態代理)

java設計模式--代理模式(靜態代理和動態代理)

完全拷貝,有些地方修改了一下
1、動態代理(Dynamic Proxy)
  代理分為靜態代理和動態代理,靜態代理是在編譯時就將介面、實現類、代理類一股腦兒全部手動完成,但如果我們需要很多的代理,每一個都這麼手動的去建立實屬浪費時間,而且會有大量的重複程式碼,此時我們就可以採用動態代理,動態代理可以在程式執行期間根據需要動態的建立代理類及其例項,來完成具體的功能。
  其實方法直接呼叫就可以完成功能,為什麼還要加個代理呢?
  原因是採用代理模式可以有效的將具體的實現與呼叫方進行解耦,通過面向介面進行編碼完全將具體的實現隱藏在內部。
2、代理實現的一般模式
  其實代理的一般模式就是靜態代理的實現模式:首先建立一個介面(JDK代理都是面向介面的),然後建立具體實現類來實現這個介面,在建立一個代理類同樣實現這個介面,不同指出在於,具體實現類的方法中需要將介面中定義的方法的業務邏輯功能實現,而代理類中的方法只要呼叫具體類中的對應方法即可,這樣我們在需要使用介面中的某個方法的功能時直接呼叫代理類的方法即可,將具體的實現類隱藏在底層。
  第一步:定義總介面Iuser.java

1 package ceshi1;
2 public interface Iuser {
3 void eat(String s);
4 }
  第二步:建立具體實現類UserImpl.java

複製程式碼
1 package ceshi1;
2 public class UserImpl implements Iuser {
3   @Override
4   public void eat(String s) {
5     System.out.println(“我要吃”+s);
6   }
7 }
複製程式碼
  第三步:建立代理類UserProxy.java

複製程式碼
1 package ceshi1;
2 public class UserProxy implements Iuser {
3   private Iuser user = new UserImpl();
4   @Override
5   public void eat(String s) {
6     System.out.println(“靜態代理前置內容”);//指的是在代理類中,可以有介面中沒有的其他方法。
7     user.eat(s);
8     System.out.println(“靜態代理後置內容”);
9   }
10 }
複製程式碼
  第四步:建立測試類ProxyTest.java

複製程式碼
1 package ceshi1;
2 public class ProxyTest {
3   public static void main(String[] args) {
4     UserProxy proxy = new UserProxy();
5     proxy.eat(“蘋果”);
6   }
7 }
複製程式碼
  執行結果:

1 靜態代理前置內容
2 我要吃蘋果
3 靜態代理後置內容
3、動態代理的實現
  動態代理的思維模式與之前的一般模式是一樣的,也是面向介面進行編碼,建立代理類將具體類隱藏解耦,不同之處在於代理類的建立時機不同,動態代理需要在執行時因需實時建立。
  第一步:定義總介面Iuser.java

1 package ceshi1;
2 public interface Iuser {
3   void eat(String s);
4 }
  第二步:建立具體實現類UserImpl.java

複製程式碼
1 package ceshi1;
2 public class UserImpl implements Iuser {
3   @Override
4   public void eat(String s) {
5     System.out.println(“我要吃”+s);
6   }
7 }
複製程式碼
  第三步:建立實現InvocationHandler介面的代理類

複製程式碼
1 package ceshi1;
2 import java.lang.reflect.InvocationHandler;
3 import java.lang.reflect.Method;
4 public class DynamicProxy implements InvocationHandler {
5   private Object object;//用於接收具體實現類的例項物件
6   //使用帶引數的構造器來傳遞具體實現類的物件
7   public DynamicProxy(Object obj){
8     this.object = obj;
9   }
10   @Override
11   public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
12     System.out.println(“前置內容”);
13     method.invoke(object, args);//關鍵的程式碼!可以將靜態代理中的proxy程式碼自動生成
14     System.out.println(“後置內容”);
15     return null;
16   }
17 }
複製程式碼
  第四步:建立測試類ProxyTest.java

複製程式碼
1 package ceshi1;
2 import java.lang.reflect.InvocationHandler;
3 import java.lang.reflect.Proxy;
4 public class ProxyTest {
5   public static void main(String[] args) {
6     Iuser user = new UserImpl();
7     InvocationHandler h = new DynamicProxy(user);
8     //Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[]{Iuser.class}, h);
IUser proxy = (IUser) Proxy.newProxyInstance(IUser.class.getClassLoader(),user.getClass().getInterfaces(),handler);//推薦使用這種方式
9     proxy.eat(“蘋果”);
10   }
11 }
複製程式碼
  執行結果為:

1 動態代理前置內容
2 我要吃蘋果
3 動態代理後置內容
4、通過上面的動態代理例項我們來仔細分析研究一下動態代理的實現過程
(1)首先我要說的就是介面,為什麼JDK的動態代理是基本介面實現的呢?
  因為通過使用介面指向實現類的例項的多型實現方式,可以有效的將具體的實現與呼叫之間解耦,便於後期修改與維護。
再具體的說就是我們在代理類中建立一個私有成員變數(private修飾),使用介面來指向實現類的物件(純種的多型體現,向上轉型的體現),然後在該代理類中的方法中使用這個建立的例項來呼叫實現類中的相應方法來完成業務邏輯功能。
這麼說起來,我之前說的“將具體實現類完全隱藏”就不怎麼正確了,可以改成,將具體實現類的細節向呼叫方完全隱藏(呼叫方呼叫的是代理類中的方法,而不是實現類中的方法)。
  這就是面向介面程式設計,利用java的多型特性,實現程式程式碼的解耦。
(2)建立代理類的過程
  如果你瞭解靜態代理,那麼你會發現動態代理的實現其實與靜態代理類似,都需要建立代理類,但是不同之處也很明顯,建立方式不同!
  不同之處體現在靜態代理我們知根知底,我們知道要對哪個介面、哪個實現類來建立代理類,所以我們在編譯前就直接實現與實現類相同的介面,直接在實現的方法中呼叫實現類中的相應(同名)方法即可;而動態代理不同,我們不知道它什麼時候建立,也不知道要建立針對哪個介面、實現類的代理類(因為它是在執行時因需實時建立的)。
  雖然二者建立時機不同,建立方式也不相同,但是原理是相同的,不同之處僅僅是:靜態代理可以直接編碼建立,而動態代理是利用反射機制來抽象出代理類的建立過程。
  讓我們來分析一下之前的程式碼來驗證一下上面的說辭:
    第一點:靜態代理需要實現與實現類相同的介面,而動態代理需要實現的是固定的Java提供的內建介面(一種專門提供來建立動態代理的介面)InvocationHandler介面,因為java在介面中提供了一個可以被自動呼叫的方法invoke,這個之後再說。
    第二點:private Object object;
        public UserProxy(Object obj){this.object = obj;}
  這幾行程式碼與靜態代理之中在代理類中定義的介面指向具體實現類的例項的程式碼異曲同工,通過這個構造器可以建立代理類的例項,建立的同時還能將具體實現類的例項與之繫結(object指的就是實現類的例項,這個例項需要在測試類中建立並作為引數來建立代理類的例項),實現了靜態代理類中private Iuser user = new UserImpl();一行程式碼的作用相近,這裡為什麼不是相同,而是相近呢,主要就是因為靜態代理的那句程式碼中包含的實現類的例項的建立,而動態代理中實現類的建立需要在測試類中完成,所以此處是相近。
    第三點:invoke(Object proxy, Method method, Object[] args)方法,該方法是InvocationHandler介面中定義的唯一方法,該方法在呼叫指定的具體方法時會自動呼叫。其引數為:代理例項、呼叫的方法、方法的引數列表
  在這個方法中我們定義了幾乎和靜態代理相同的內容,僅僅是在方法的呼叫上不同,不同的原因與之前分析的一樣(建立時機的不同,建立的方式的不同,即反射),Method類是反射機制中一個重要的類,用於封裝方法,該類中有一個方法那就是invoke(Object object,Object…args)方法,其引數分別表示:所呼叫方法所屬的類的物件和方法的引數列表,這裡的引數列表正是從測試類中傳遞到代理類中的invoke方法三個引數中最後一個引數(呼叫方法的引數列表)中,在傳遞到method的invoke方法中的第二個引數中的(此處有點囉嗦)。
    第四點:測試類中的異同
  靜態代理中我們測試類中直接建立代理類的物件,使用代理類的物件來呼叫其方法即可,若是別的介面(這裡指的是別的呼叫方)要呼叫Iuser的方法,也可以使用此法
動態代理中要複雜的多,首先我們要將之前提到的實現類的例項建立(補充完整),然後利用這個例項作為引數,呼叫代理來的帶參構造器來建立“代理類例項物件”,這裡加引號的原因是因為它並不是真正的代理類的例項物件,而是建立真正代理類例項的一個引數,這個實現了InvocationHandler介面的類嚴格意義上來說並不是代理類,我們可以將其看作是建立代理類的必備中間環節,這是一個呼叫處理器,也就是處理方法呼叫的一個類,不是真正意義上的代理類,可以這麼說:建立一個方法呼叫處理器例項。
  下面才是真正的代理類例項的建立,之前建立的”代理類例項物件“僅僅是一個引數
    Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[]{Iuser.class}, h);
  這裡使用了動態代理所依賴的第二個重要類Proxy,此處使用了其靜態方法來建立一個代理例項,其引數分別是:類載入器(可為父類的類載入器)、介面陣列、方法呼叫處理器例項
  這裡同樣使用了多型,使用介面指向代理類的例項,最後會用該例項來進行具體方法的呼叫即可。