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

聲明:原創(chuàng)作品,轉載請注明出處http://www.itdecent.cn/p/e4c1e6b734ad

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

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

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

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

public interface Subject {
    void buyTicket();
}

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

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("買了一份保險");
    }
}

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

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

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

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

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

動態(tài)代理模式

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

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

public interface Subject {
    void buyTicket();
}

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

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

你也發(fā)現(xiàn)了,其實這兩個類就是上面的,沒有做任何修改,接下來我們還需要定義一個代理類,由于這是動態(tài)代理模式,這個代理類不需要我們手動編寫。注意不需要我們手動編寫,并不表示不存在這個代理類,而是由虛擬機自動生成的。那么我們如何獲取到這個代理類呢,很簡單我們只需要調用Proxy類的newProxyInstance方法,Proxy類是由jdk提供,我們可以直接使用,這個方法需要傳入三個參數(shù),第一個參數(shù)為目標類的類加載器,所謂類加載器是虛擬機用來加載類的,第二個為目標類所實現(xiàn)的接口數(shù)組,第三個參數(shù)需要傳入一個對象,這個對象所對應的類需要實現(xiàn)jdk提供給我們的InvocationHandler接口,然后復寫里面invoke方法,當動態(tài)代理類中代理方法被調用時,會執(zhí)行這個invoke方法。實現(xiàn)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);

這樣第三個參數(shù)已經(jīng)有了,接下來我們就可以獲取由虛擬機為我們生成的代理對象了:

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

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

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

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

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

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

動態(tài)代理實現(xiàn)原理

反射

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

public class Person {
    public String name;
    private int age;
    public Person(String name){
        System.out.print("有參數(shù)實例化\n");
        this.name = name;
    }
    public Person(){
        System.out.print("無參數(shù)實例化\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類,里面定義了幾個構造方法,一個有參數(shù)一個沒有,還有一個私有的帶參數(shù)的構造方法,另外還有兩個方法,一個方法為獲取Person的名字,一個是讓這個Person跑步,如果我們希望讓一個Person跑起來,該怎么做呢,很簡單,我們只需要new 一個Person對象,然后調用其run方法就可以了,如下:

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

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

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

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

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

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

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

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

這種方式不足的地方在于獲取Class對象前你還要創(chuàng)建一個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方法:

輸出結果
====================================
無參數(shù)實例化
跑步
====================================

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

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

我們挨個來看下,

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

這個方法返回權限為public的構造方法對象,需要傳入?yún)?shù)類型的Class對象,比如我們想要調用Person類中的Person(String name)構造方法可以通過以下方式來實現(xiàn):

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參數(shù),因為我們要調用的構造方法參數(shù)為String類型的,這樣就得到了一個Constructor對象,接著我們就可以調用Constructor對象的newInstance方法并傳入name參數(shù),來實例化一個Person對象,獲取到Perosn對象,就可以調用里面的方法啦。

2. getConstructors()

這個方法會返回所有權限為public的構造方法的Constructor對象數(shù)組。

Constructor<Person>[] constructors = (Constructor<Person>[]) clazz.getConstructors();
for (int i = 0; i < constructors.length; i++) {
    System.out.println("構造函數(shù)["+i+"]:"+constructors[i].toString() );
}
--------------------------------------------------
輸出結果:
構造函數(shù)[0]:public com.modoutech.designpattern.Person()
構造函數(shù)[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()

這個方法獲取類中已申明的所有構造方法對象數(shù)組,包括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的字段,包含繼承字段

我們來挨個看下:
為了演示方便我們再創(chuàng)建一個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());
-------------------------
輸出結果:
無參數(shù)實例化
course is 語文
-------------------------

上述,我們先獲取了Student的Class對象,并實例化一個Student對象,然后通過Class的getDeclaredField方法,并傳入course這個字段名,然后獲取一個Field對象,由于course這個字段為private,所以還需要調用他的setAccessible并傳入true,來打開它的訪問權限,之后就可以調用Field的set方法來設置這個字段的值,這個set方法需兩個參數(shù),第一個為需要設置的實例對象,第二個為需要設置的字段值。這樣我們就完成了對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
---------------------------------------------

以上我們獲取了字段數(shù)組,并打印了每個字段名,可以看到我們只獲取到了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());

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

可以看到我們成功獲取了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方法外,F(xiàn)ield還有其他的一些方法,部分方法如下:

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

---------------------------------------------
輸出結果:
無參數(shù)實例化
跑步
---------------------------------------------

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

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

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

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

源碼解析

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

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

想必你一定很好奇這個方法是如何創(chuàng)建一個代理類的,我們馬上進入到這個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對象,難道這個對象就是動態(tài)代理類的Class對象嗎?先別急我們再往下看看,下面通過剛才獲取到的Class對象獲取一個Constructor對象,然后把這個Constructor對象和之前傳入的InvocationHandler 對象作為參數(shù)傳入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 作為參數(shù)來獲取一個實例對象并返回。顯然這個對象就是我們想要的動態(tài)代理對象。這樣我們也就知道了上面調用getProxyClass0方法獲取的Class對象就是動態(tài)代理的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);

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


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

我們發(fā)現(xiàn)這個方法是native的,Java沒有給我們提供他的源代碼。有的朋友可能還是很好奇,這個方法到底執(zhí)行了什么,我們知道我們編寫好一個類后,然后編譯會生成一個.class的字節(jié)碼文件。這個文件說白了都是一些字節(jié)數(shù)據(jù),而這個方法就是為我們生成這個數(shù)據(jù),當然這些數(shù)據(jù)不是隨意生成的,而是遵守一定規(guī)則的。至于這個文件里面具體是怎樣的以及具體遵守什么樣的規(guī)則可以參考《java 虛擬機規(guī)范》這個書,里面介紹了class文件的格式等底層知識。對這部分感興趣的同學可以參考下。

到這里其實有關代理類的動態(tài)生成的源碼部分已經(jīng)分析的差不多了,可能有些朋友有些失落,不能完全了解其具體生成的代理類。不過沒關系,我們知道了動態(tài)代理的使用及運行機制,我們可以試著自己模擬寫出一個動態(tài)代理類,還是拿上面買火車票來舉個例子,首先這個動態(tài)代理類中肯定實現(xiàn)了Subject接口并有一個買票的方法,接下來由上面的源碼分析我們可以知道這個動態(tài)代理類有一個構造方法,他的參數(shù)是一個InvocationHandler 對象,很顯然當我們調用動態(tài)代理的買票方法就會調用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 {
            //這里由于我們定義的買票方法是無參數(shù)方法,所以這里getMethod方法就傳入方法名,如果有參數(shù)則還需傳入?yún)?shù)的對應類型
            method = Subject.class.getMethod("buyTicket");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        try {
            mInvocationHandler.invoke(new Object(),method,null);
        } catch (Throwable throwable) {
        }
    }
}

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

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

設計模式持續(xù)更新中...

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,534評論 19 139
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,638評論 18 399
  • Scala與Java的關系 Scala與Java的關系是非常緊密的??! 因為Scala是基于Java虛擬機,也就是...
    燈火gg閱讀 3,607評論 1 24
  • 王瀅雅,又名王一丁,屬龍,二0一二年二月四日(農歷龍年正月十三日)生於沈陽二O二醫(yī)院。天真活潑,招人喜愛。 ...
    廣仁閱讀 829評論 0 2

友情鏈接更多精彩內容