Java 大白話講解設計模式之 -- 代理模式

聲明:原創作品,轉載請註明出處https://www.jianshu.com/p/e4c1e6b734ad

今天來總結下代理模式,所謂“代理”,顧名思義就是代替你處理某事。當我們無法直接做某事的時候就可以創建一個代理來間接的完成這件事情。

代理模式在我們生活中也隨處可見。相信每個人都搶過火車票。買火車票最直接的方式就是去火車站買,但是有的人離火車站比較遠,不太方便,一般都選擇用搶票軟件購買。其中搶票軟件就起到了代理的作用。搶票軟件和火車站都有同一個功能,就是可以買到火車票。當然了,搶票軟件還有一些其他的功能,比如有些搶票軟件在你買票時會非常“貼心”的幫你買上保險。。。

接下來,我們來看下如何用代碼來實現上述買票的例子。

首先我們需要定義一個接口,裏面有一個買票的方法:

public interface Subject {
    void buyTicket();
}

接下來創建一個火車站類,以及代理買票的搶票軟件類,因爲這兩個類都是用來買票的,所以需要實現上面的買票接口:
火車站類:

public class TrainStation implements Subject {
    @Override
    public void buyTicket() {
        System.out.print("成功買到一張票\n");
    }
}

搶票軟件類:

public class TicketSoftware implements Subject{
    TrainStation trainStation;
    @Override
    public void buyTicket() {
        if (trainStation == null){
            trainStation = new TrainStation();
        }
        trainStation.buyTicket();
        buyInsurance();
    }

    public void buyInsurance(){
        System.out.print("買了一份保險");
    }
}

可以看到,搶票軟件就是我們的代理類,裏面的買票方法其實就是調用火車站類中的買票方法,這也符合情理,畢竟我們用的搶票軟件都需要通過我們國家鐵路系統買的。然後買完票後,搶票軟件還給你買個了保險。。。

好了,接下來我們來看下,這個搶票軟件是否管用:

TicketSoftware ticketSoftware = new TicketSoftware(); 
ticketSoftware.buyTicket();

輸出結果:
----------------------
成功買到一張票
買了一份保險
----------------------

我們的搶票軟件起作用了,看來以後就不用再去火車站啦!

動態代理模式

以上就是簡單的靜態代理模式,所謂靜態,就是這個TicketSoftware 代理類是我們事先寫好的,而動態代理模式中,代理類是虛擬機在運行時自動創建的。接下來我們來看一下,如何用動態代理來實現上述的例子

和上面一樣,我們需要一個接口,裏面定義了一個買票方法:

public interface Subject {
    void buyTicket();
}

然後定義一個目標類,即火車站類,真正買票操作是在這個類中完成的:

public class TrainStation implements Subject {
    @Override
    public void buyTicket() {
        System.out.print("成功買到一張票\n");
    }
}

你也發現了,其實這兩個類就是上面的,沒有做任何修改,接下來我們還需要定義一個代理類,由於這是動態代理模式,這個代理類不需要我們手動編寫。注意不需要我們手動編寫,並不表示不存在這個代理類,而是由虛擬機自動生成的。那麼我們如何獲取到這個代理類呢,很簡單我們只需要調用Proxy類的newProxyInstance方法,Proxy類是由jdk提供,我們可以直接使用,這個方法需要傳入三個參數,第一個參數爲目標類的類加載器,所謂類加載器是虛擬機用來加載類的,第二個爲目標類所實現的接口數組,第三個參數需要傳入一個對象,這個對象所對應的類需要實現jdk提供給我們的InvocationHandler接口,然後複寫裏面invoke方法,當動態代理類中代理方法被調用時,會執行這個invoke方法。實現InvocationHandler接口完整的類如下:

class MyInvocationHandler implements InvocationHandler {
        // 需要被代理的那個對象
        private Object target;
        // 用於傳入目標對象TrainStation 
        public void setTarget(Object target){
            this.target = target;
        }
        // 當我們去調用代理對象的方法時,invoke 方法將會取而代之。
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result = method.invoke(target,args);//實際上調用了目標對象TrainStation 買火車票的方法
            buyInsurance();//買完票後有買了一份保險
            return result;
        }
        public void buyInsurance(){
            System.out.print("買了一份保險");
        }
    }

這個類還是很簡單的,主要的就是他的invoke方法,在這個方法中,我們通過method.invoke(target,args)方法調用了目標對象的方法,在這裏也就是火車站對象的買票方法。有關method.invoke是反射的內容稍後會提到。之後我們又調用了買保險的方法。

好了,定義完MyInvocationHandler類,然後實例化MyInvocationHandler對象,並傳入一個目標對象:

TrainStation trainStation = new TrainStation();
MyInvocationHandler handler = new MyInvocationHandler();
handler.setTarget(trainStation);

這樣第三個參數已經有了,接下來我們就可以獲取由虛擬機爲我們生成的代理對象了:

Subject proxy = (Subject)Proxy.newProxyInstance(trainStation.getClass().getClassLoader(),trainStation.getClass().getInterfaces(),handler);

這個動態生成的代理對象的類在底層實現了Subject接口,但Proxy.newProxyInstance返回的對象是Object類型,所以我們還需要一次類型轉換。

好了有了代理對象,我們就可以像之前的靜態代理一樣調用代理對象的買票方法:

proxy.buyTicket();//調用代理對象的買票方法

輸出結果:
----------------------
成功買到一張票
買了一份保險
----------------------

可以看到我們的動態代理生效了,當我們調用代理類的buyTicket方法,我們之前定義的MyInvocationHandler對象的invoke方法就會執行。你是不是感到很神奇,我們並沒有定義代理類,那它究竟是如何工作的呢。別急,接下來我們就一起來看下動態代理的工作原理。

動態代理實現原理

反射

要想理解動態代理的實現原理,我們還得先來理解反射。什麼是反射呢?一般我們操作一個對象時,都會先new一個,比如現在有一個Person類:

public class Person {
    public String name;
    private int age;
    public Person(String name){
        System.out.print("有參數實例化\n");
        this.name = name;
    }
    public Person(){
        System.out.print("無參數實例化\n");
    }
    
    private Person(String name,int age){
        this.name = name;
        this.age = age;
    }
    public void run(){
        System.out.print("跑步");
    }
    public String getName() {
        return name;
    }
}

如上,我們定義了一個Person類,裏面定義了幾個構造方法,一個有參數一個沒有,還有一個私有的帶參數的構造方法,另外還有兩個方法,一個方法爲獲取Person的名字,一個是讓這個Person跑步,如果我們希望讓一個Person跑起來,該怎麼做呢,很簡單,我們只需要new 一個Person對象,然後調用其run方法就可以了,如下:

Person perosn = new Person("張三");
person.run();

這樣就讓一個Person跑起來了,但是問題來了,如果在程序運行的時候,我需要操作的不一定是Person,而是一個會變得類,這個類可能是Person也可能是其他類比如叫做Bird或者Dog等等,而我們要操作的方法也不一定是run,也有可能是fly,sleep等等其他方法。那這時怎麼辦,很顯然,我們是無法事先new 一個對象,因爲我們根本就不知道程序運行的時候需要操作什麼對象,執行什麼方法,這時我們就可以用上反射了,反射可以使你在運行時讀取類或對象的信息,也可以在運行時創建對象,操作對象的字段屬性與方法。

好了理解了什麼是反射,接下來,我們來看下如何使用反射。要想使用反射,你需要用到一個類,即Class類。什麼是Class類呢?我們知道Java和C語言最大的不同就是,C是面向過程的,而Java是面向對象的。在Java的世界裏,一切皆對象,一切都是可描述的,只要你能想到的,不管什麼,我們都可以用類來描述,那麼問題來了,我們知道類也是客觀存在的東西,那麼用什麼來描述類的,答案就是Class類,即一個描述類的類,這個類的名字爲Class。這是一個實際存在的類,路徑爲JDK的java.lang包中,感興趣的朋友可以去看下。所謂存在即合理,這個Class類肯定不是Java工程師閒的蛋疼寫出來的,而是有用的,拿上面的Person類舉個例子,一開始我們通過編碼生成了一個Person.java文件,然後通過編譯又生成了一個Person.class字節碼文件,這個文件就保存了一個Class對象,對象中包含了Person類的所有信息,當我們調用new Person()時,虛擬機就會檢查內存中是否加載了Person.class這個字節碼文件,如果沒有就會加載,此時這個Class對象也會被加載到內存中,當虛擬機實例化Person對象時,虛擬機需要知道一些信息,比如要實例化的類中有什麼字段什麼方法等等,這些信息都被保存在Class對象中。所以說Class類對於虛擬機來說是個很重要的類。同樣在反射中,我們也需要知道Person類的信息,這自然就離不開Class對象。

那麼我們如何獲取到Class類的對象呢,一共有三種方式,拿上面Person類舉例子:
第一種,Class類有一個forName(String className)方法,需要傳入一個類的完整類名,返回爲該類型的Class對象,如下:

Class clazz = Class.forName("包名.Person");

注意,調用forName時,你需要捕獲一個叫做ClassNotFoundException無法找到該類的異常。

第二種,你需要先創建一個Person對象,然後調用Person對象的getClass方法,就可以獲取到Class對象:

  Person person = new Person("張三");
  Class clazz = person.getClass();

這種方式不足的地方在於獲取Class對象前你還要創建一個Person對象。

第三種,最簡單也是最安全的一種方式爲直接調用Person.class,如下:

Class clazz = Person.class;

以上簡單說明了下獲取Class對象的幾種方式,我們之前說過Class對象保存了對應類的類信息,包括這個類的構造方法、字段屬性,類中的方法等。接下來我們來看下,如何使用Class對象來獲取以及操作這些類信息。

構造方法

首先我們來看下構造方法,在反射中要想實例化一個類其實還是調用該類構造方法,只不過不是簡單new下就可以了。我們先來看一種簡單的實例化方式:

Class<?> clazz = Class.forName("包名.Person");
Person person = (Person) clazz.newInstance();
person.run();

上面的代碼很簡單,我們先調用Class的forName方法來獲取Person類的Class對象,然後直接調用該對象的newInstance方法,這樣就獲取到了一個Person實例,然後調用他的run方法:

輸出結果
====================================
無參數實例化
跑步
====================================

可以看到Person的構造方法成功被調用了,不過需要注意的是clazz.newInstance只適用於無參數構造方法,有參的我們需要通過Class獲取Constructor對象來操作,Constructor對象是對構造方法的封裝,Class獲取Constructor對象的主要方法有以下幾種:

返回值 方法名稱 說明
Constructor<T> getConstructor(Class<?>... parameterTypes) 返回指定參數類型、具有public訪問權限的構造函數對象
Constructor<?>[] getConstructors() 返回所有具有public訪問權限的構造函數的Constructor對象數組
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 返回指定參數類型、所有聲明的(包括private)構造函數對象
Constructor<?>[] getDeclaredConstructor() 返回所有聲明的(包括private)構造函數對象

我們挨個來看下,

1. getConstructor(Class<?>... parameterTypes)

這個方法返回權限爲public的構造方法對象,需要傳入參數類型的Class對象,比如我們想要調用Person類中的Person(String name)構造方法可以通過以下方式來實現:

Class<?> clazz = Class.forName("com.modoutech.designpattern.Person");
Constructor<Person> constructor = (Constructor<Person>) clazz.getConstructor(String.class);
Person person1 = constructor.newInstance("張三");
person1.run();

可以看到我們先獲取了Person的Class對象,然後調用Class的getConstructor方法,同時傳入String.class參數,因爲我們要調用的構造方法參數爲String類型的,這樣就得到了一個Constructor對象,接着我們就可以調用Constructor對象的newInstance方法並傳入name參數,來實例化一個Person對象,獲取到Perosn對象,就可以調用裏面的方法啦。

2. getConstructors()

這個方法會返回所有權限爲public的構造方法的Constructor對象數組。

Constructor<Person>[] constructors = (Constructor<Person>[]) clazz.getConstructors();
for (int i = 0; i < constructors.length; i++) {
    System.out.println("構造函數["+i+"]:"+constructors[i].toString() );
}
--------------------------------------------------
輸出結果:
構造函數[0]:public com.modoutech.designpattern.Person()
構造函數[1]:public com.modoutech.designpattern.Person(java.lang.String)
--------------------------------------------------

可以看到我們獲取了兩個Constructor對象,這兩個也正是對應了Person中權限爲public的構造方法,至於接下來對這兩個構造方法調用和上面的方法同理這裏就不在贅述了。

3. getDeclaredConstructor(Class<?>... parameterTypes)

這個方法範圍要大點,會返回類中申明的任何構造方法對象,包括權限爲private的。比如我們想調用Person中私有的Person(String name,int age)構造方法,可以通過如下方式:

Constructor<Person> constructor = (Constructor<Person>) clazz.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
Person person1 = constructor.newInstance("張三",23);
person1.run();

這裏基本和上述步驟相同,不過需要注意的是訪問私有的構造方法時,我們需要調用Constructor的setAccessible方法,並設置爲true,表示可以訪問該私有構造方法。

4. getDeclaredConstructor()

這個方法獲取類中已申明的所有構造方法對象數組,包括private的,具體操作方法與上述相同,這裏不再贅述。

好了這樣我們簡單的介紹了用Constructor在反射中實例化對象的幾種方式,說完構造方法,接下來我們來看下如何獲取以及操作類中的字段屬性

字段Field

如果我們要想操作類或對象中的字段,那麼就需要用到Field對象。同樣這個Field對象也是通過Class獲取的,以下是Class獲取Field對象的幾種方法:

返回值 方法名稱 說明
Field getDeclaredField(String name) 獲取指定name名稱的(包含private修飾的)字段,不包括繼承的字段
Field[] getDeclaredField() 獲取Class對象所表示的類或接口的所有(包含private修飾的)字段,不包括繼承的字段
Field getField(String name) 獲取指定name名稱、具有public修飾的字段,包含繼承字段
Field[] getField() 獲取修飾符爲public的字段,包含繼承字段

我們來挨個看下:
爲了演示方便我們再創建一個Student類,繼承自上述的Person類:

public class Student extends Person {
    private String course;
    public int score;

    public String getCourse() {
        return course;
    }

    public void setCourse(String course) {
        this.course = course;
    }
}

1.getDeclaredField(String name)

獲取指定名稱的(包含private修飾的)字段,不包括從父類中繼承的字段。

用這個方法,我們可以在Student類中,獲取course和score字段,但是無法獲取它父類Person中的字段。拿course舉個例子:

Class<?> clazz = Class.forName("com.modoutech.designpattern.Student");
Student student = (Student) clazz.newInstance();
Field courseField = clazz.getDeclaredField("course");
courseField.setAccessible(true);
courseField.set(student,"語文");
System.out.print("course is "+student.getCourse());
-------------------------
輸出結果:
無參數實例化
course is 語文
-------------------------

上述,我們先獲取了Student的Class對象,並實例化一個Student對象,然後通過Class的getDeclaredField方法,並傳入course這個字段名,然後獲取一個Field對象,由於course這個字段爲private,所以還需要調用他的setAccessible並傳入true,來打開它的訪問權限,之後就可以調用Field的set方法來設置這個字段的值,這個set方法需兩個參數,第一個爲需要設置的實例對象,第二個爲需要設置的字段值。這樣我們就完成了對course這個字段的賦值操作。

2. getDeclaredField()

這個方法用於獲取Class對象所表示的類或接口的所有(包含private修飾的)字段,不包括繼承的字段:

Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
    System.out.print(fields[i].getName()+"\n");
}
---------------------------------------------
輸出結果:
course
score
---------------------------------------------

以上我們獲取了字段數組,並打印了每個字段名,可以看到我們只獲取到了Student類中的course和score字段,並沒有他父類的字段。

3. getField(String name)

這個方法獲取指定名稱的public字段,包括父類中的也可以獲取到。接下來我們試着獲取下Student父類中的name字段:

Class<?> clazz = Class.forName("com.modoutech.designpattern.Student");
Student student = (Student) clazz.newInstance();
Field field = clazz.getField("name");
field.set(student,"張三");
System.out.print("學生名字:"+student.getName());

---------------------------------------------------------------------------
輸出結果:
無參數實例化
學生名字:張三
--------------------------------------------------------------------------

可以看到我們成功獲取了Student父類中的字段,並做了賦值操作。

3. getField()

這個方法時獲取所有public字段,包括父類中的:

Field[] fields = clazz.getFields();
for (int i = 0; i < fields.length; i++) {
    System.out.print("字段名:"+fields[i].getName()+"\n");
}

---------------------------------------------------------------
輸出結果:
字段名:score
字段名:name
---------------------------------------------------------------

可以看到,我們獲取到了Student中聲明的public字段score,同時還獲取到了其父類中public字段name,至於對獲取到的字段進行進一步的操作,和上面的一樣,就不在贅述了。

在上面,可以看到,我們調用了Field 的set方法來設置字段的值,當然除了set方法外,Field還有其他的一些方法,部分方法如下:

方法返回值 方法名稱 方法說明
Object get(Object obj) 返回指定對象上此 Field 表示的字段的值
Class<?> getType() 返回一個 Class 對象,它標識了此Field 對象所表示字段的聲明類型。
boolean isEnumConstant() 如果此字段表示枚舉類型的元素則返回 true;否則返回 false
String toGenericString() 返回一個描述此 Field(包括其一般類型)的字符串
String getName() 返回此 Field 對象表示的字段的名稱
Class<?> getDeclaringClass() 返回表示類或接口的 Class 對象,該類或接口聲明由此 Field 對象表示的字段
Method(方法)

如果我們想要操作類中的某個方法,我們可以藉助Method這個類,這個類是對類或接口中某個方法的描述,我們可以通過調用Class對象的以下方法來獲取Method對象:

方法返回值 方法名稱 方法說明
Method getDeclaredMethod(String name, Class<?>... parameterTypes) 返回一個指定參數的Method對象,該對象反映此 Class 對象所表示的類或接口的指定已聲明方法。
Method[] getDeclaredMethod() 返回 Method 對象的一個數組,這些對象反映此 Class 對象表示的類或接口聲明的所有方法,包括公共、保護、默認(包)訪問和私有方法,但不包括繼承的方法。
Method getMethod(String name, Class<?>... parameterTypes) 返回一個 Method 對象,它反映此 Class 對象所表示的類或接口的指定公共成員方法。
Method[] getMethods() 返回一個包含某些 Method 對象的數組,這些對象反映此 Class 對象所表示的類或接口(包括那些由該類或接口聲明的以及從超類和超接口繼承的那些的類或接口)的公共 member 方法。

以上簡單的列舉了獲取Method對象的一些方法,具體獲取方式就不演示了,和上面的操作步驟差不多。

接下來,我們來看下如何利用Method對象來調用類中方法。拿上面Person類舉個例子,來看下如果通過Method來調用裏面的run方法:

Class<?> clazz = Class.forName("com.modoutech.designpattern.Student");
Person person = (Person) clazz.newInstance();
Method method = clazz.getDeclaredMethod("run");
method.invoke(person);

---------------------------------------------
輸出結果:
無參數實例化
跑步
---------------------------------------------

可以看到我們成功通過反射來調用了Person中的run方法,當然如果這個方法是private的,那麼在調用該方法前需要調用Method的setAccessible方法,並傳入true來打開該方法的訪問權限。
當然除了上述介紹的一些方法外,Method還提供了其他的一些方法:

方法返回值 方法名稱 方法說明
Object invoke(Object obj, Object... args) 對帶有指定參數的指定對象調用由此 Method 對象表示的底層方法。
Class<?> getReturnType() 返回一個 Class 對象,該對象描述了此 Method 對象所表示的方法的正式返回類型,即方法的返回類型
Type getGenericReturnType() 返回表示由此 Method 對象所表示方法的正式返回類型的 Type 對象,也是方法的返回類型。
Class<?>[] getParameterTypes() 按照聲明順序返回 Class 對象的數組,這些對象描述了此 Method 對象所表示的方法的形參類型。即返回方法的參數類型組成的數組
Type[] getGenericParameterTypes() 按照聲明順序返回 Type 對象的數組,這些對象描述了此 Method 對象所表示的方法的形參類型的,也是返回方法的參數類型
String getParameterTypes() 按照聲明順序返回 Class 對象的數組,這些對象描述了此 Method 對象所表示的方法的形參類型。即返回方法的參數類型組成的數組
Class<?>[] getName() 以 String 形式返回此 Method 對象表示的方法名稱,即返回方法的名稱
boolean isVarArgs() 判斷方法是否帶可變參數,如果將此方法聲明爲帶有可變數量的參數,則返回 true;否則,返回 false。
String toGenericString() 返回描述此 Method 的字符串,包括類型參數。

以上就是一些常用的方法,這裏就不一一展開來講了,如果想更詳細的瞭解可以查閱官方API。

好了,這樣就簡單的介紹了下Java反射的一些內容。

源碼解析

簡單瞭解了反射知識後,我們來看下動態代理底層實現原理。上面我們知道,動態代理和靜態代理的區別主要在於代理類的區別,靜態代理的代理類是我們自己手動編碼生成的,而動態代理的代理類是由虛擬機幫我們生成的。對於動態代理,我們主要通過調用Proxy的newProxyInstance方法來獲取動態代理類:

Subject proxy = (Subject)Proxy.newProxyInstance(trainStation.getClass().getClassLoader(),trainStation.getClass().getInterfaces(),handler);

想必你一定很好奇這個方法是如何創建一個代理類的,我們馬上進入到這個newProxyInstance來看一下:

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        if (h == null) {
            throw new NullPointerException();
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, interfaces);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            return newInstance(cons, h);
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        }
    }

可以看到裏面的代碼還是比較少的,前面做了下非空判斷,然後調用了getProxyClass0方法獲取了一個Class對象,難道這個對象就是動態代理類的Class對象嗎?先別急我們再往下看看,下面通過剛纔獲取到的Class對象獲取一個Constructor對象,然後把這個Constructor對象和之前傳入的InvocationHandler 對象作爲參數傳入newInstance方法並返回。這樣這個方法就結束,我們再進入到newInstance方法看下:


    private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
        try {
            return cons.newInstance(new Object[] {h} );
        } catch (IllegalAccessException | InstantiationException e) {
            throw new InternalError(e.toString());
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString());
            }
        }
    }

可以看到這個方法裏的代碼就更簡單了,就是調用之前傳入的Constructor對象的newInstance方法,並把InvocationHandler 作爲參數來獲取一個實例對象並返回。顯然這個對象就是我們想要的動態代理對象。這樣我們也就知道了上面調用getProxyClass0方法獲取的Class對象就是動態代理的Class對象。那麼我們的重點就集中在了這個方法,我們接着就進入到這個方法看下:

       try {
            String proxyPkg = null;     // package to define proxy class in

            /*
             * Record the package of a non-public proxy interface so that the
             * proxy class will be defined in the same package.  Verify that
             * all non-public proxy interfaces are in the same package.
             */
            for (int i = 0; i < interfaces.length; i++) {
                int flags = interfaces[i].getModifiers();
                if (!Modifier.isPublic(flags)) {
                    String name = interfaces[i].getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                // if no non-public proxy interfaces, use the default package.
                proxyPkg = "";
            }

            {
                // Android-changed: Generate the proxy directly instead of calling
                // through to ProxyGenerator.
                List<Method> methods = getMethods(interfaces);
                Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE);
                validateReturnTypes(methods);
                List<Class<?>[]> exceptions = deduplicateAndGetExceptions(methods);

                Method[] methodsArray = methods.toArray(new Method[methods.size()]);
                Class<?>[][] exceptionsArray = exceptions.toArray(new Class<?>[exceptions.size()][]);

                /*
                 * Choose a name for the proxy class to generate.
                 */
                final long num;
                synchronized (nextUniqueNumberLock) {
                    num = nextUniqueNumber++;
                }
                String proxyName = proxyPkg + proxyClassNamePrefix + num;

                proxyClass = generateProxy(proxyName, interfaces, loader, methodsArray,
                        exceptionsArray);
            }
            // add to set of all generated proxy classes, for isProxyClass
            proxyClasses.put(proxyClass, null);

這個方法裏面的代碼比較多,我截取了重點部分,看上去這裏的代碼還是很多,不過別擔心這些代碼其實都在爲其中的一條語句做準備:

proxyClass = generateProxy(proxyName, interfaces, loader, methodsArray, exceptionsArray);

這條語句就是創建動態類的語句,其中interfaces和loader是之前傳入的,上面的代碼其實就是生成類名proxyName,方法數組methodsArray以及異常數組exceptionsArray,這樣生成一個類的所有元素就具備了,然後就是調用generateProxy方法來生成動態代理類的Class對象。我們接着在進入這個方法看下,究竟發生了什麼:


private static native Class<?> generateProxy(String name, Class<?>[] interfaces,
                                                 ClassLoader loader, Method[] methods,
                                                 Class<?>[][] exceptions);

我們發現這個方法是native的,Java沒有給我們提供他的源代碼。有的朋友可能還是很好奇,這個方法到底執行了什麼,我們知道我們編寫好一個類後,然後編譯會生成一個.class的字節碼文件。這個文件說白了都是一些字節數據,而這個方法就是爲我們生成這個數據,當然這些數據不是隨意生成的,而是遵守一定規則的。至於這個文件裏面具體是怎樣的以及具體遵守什麼樣的規則可以參考《java 虛擬機規範》這個書,裏面介紹了class文件的格式等底層知識。對這部分感興趣的同學可以參考下。

到這裏其實有關代理類的動態生成的源碼部分已經分析的差不多了,可能有些朋友有些失落,不能完全瞭解其具體生成的代理類。不過沒關係,我們知道了動態代理的使用及運行機制,我們可以試着自己模擬寫出一個動態代理類,還是拿上面買火車票來舉個例子,首先這個動態代理類中肯定實現了Subject接口並有一個買票的方法,接下來由上面的源碼分析我們可以知道這個動態代理類有一個構造方法,他的參數是一個InvocationHandler 對象,很顯然當我們調用動態代理的買票方法就會調用InvocationHandler 的invoke方法。模擬的代碼如下:

public class MyProxyClass implements Subject{
    private final InvocationHandler mInvocationHandler;

    public MyProxyClass(InvocationHandler invocationHandler){
        this.mInvocationHandler = invocationHandler;
    }

    public void proxyMethod(Method method,Object[] args){
        try {
            mInvocationHandler.invoke(new Object(),method,args);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

    @Override
    public void buyTicket() {
        Method method = null;
        try {
            //這裏由於我們定義的買票方法是無參數方法,所以這裏getMethod方法就傳入方法名,如果有參數則還需傳入參數的對應類型
            method = Subject.class.getMethod("buyTicket");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        try {
            mInvocationHandler.invoke(new Object(),method,null);
        } catch (Throwable throwable) {
        }
    }
}

以上就是對動態代理類進行了模擬,雖然不能說是和動態生成的代理類一模一樣,但是運行原理是一樣的。這樣有關動態代理類的分析也就差不多了,不過動態代理有個小小的不足,就是隻能代理interface無法代理class,因爲每個動態生成的代理類都已經繼承了Proxy類,如果再代理class的話,就會出現多繼承現象,而Java是不支持多繼承的。不過相比動態代理的強大,這點問題還是微不足道的。

好了有關設計模式的代理模式部分到這也就差不多了。

設計模式持續更新中...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章