Android進階學習(2)-- 反射及動態代理模式

什麼是反射

“反射”之中包含了一個“反”字,我們先來看看什麼是“正”;一般情況下,我們使用某個類會知道他是幹什麼的,裏面有哪些屬性和方法,在使用時對這個類實例化,之後用實例化的對象來操作。
反射,則是一開始我們不知道要使用的類是什麼,所以不能使用new關鍵字來實例化,更不會知道類中包含了哪些方法、屬性。這時候就需要使用JDK提供的“反射”API進行反射調用。
反射就是在運行時才知道要操作的類是什麼,在運行時獲取完整的構造,屬性,並調用方法。
反射也是Java語言被視爲動態語言的關鍵。

Class類

Class是一個類,封裝了當前對象所對應的類的信息。我們寫的所有的類,其實都是一個Class的對象,Class就是來描述所有的類。類的所有信息,會在編譯的時候加在java類文件的末尾,包括它的構造,方法,屬性。

獲取Class類

獲取Class類的方法有三種:

  1. 通過類名獲取 —— 類名.Class
  2. 通過對象名獲取 —— 對象名.Class
  3. 通過全類名獲取 —— Class.forName(“com.xxx.xxx”)

通過new關鍵字創建的對象,和通過Class創建的對象是完全一樣的:

Person p = new Person("shy",23);
p.getAge();
Log.e("通過new創建Person", "name = " + p.getName());
//通過下面 三種方式創建的Class對象是一樣的
//Class class1 = Person.class;
//Class class2 = p.getClass();
try { //全類名可能找不到 android裏需要try catch
    Class class3 = Class.forName("com.example.study.Person");
    Person person = (Person) class3.newInstance();
    person.setName("SHY");
    Log.e("通過Class創建Person", "name = " + person.getName());
} catch (Exception e) { 
    e.printStackTrace();
}

輸出:
在這裏插入圖片描述
這裏要說明一下,在Java中類的信息(構造,屬性,方法)都有對應的類來表示(Field:屬性,Constructor:構造,Method:方法)。通過這些類的對象,我們可以獲取到類的信息。
在這裏插入圖片描述

通過反射獲取構造方法

首先我們先創建一個Person類,裏面包含一些屬性和方法

public class Person {

    String name;
    private int age;

    public Person(){
        super();
    }

    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    private void printAll(){
        Log.e("printAll被調用","printAll被調用");
    }
}

獲取構造方法的思路,根據上面的圖片,我們可以得知Constructor對象包含在Class中,所以我們獲取到類的Class對象,在獲取Constructor即可

try {
    Class<Person> c = (Class<Person>) Class.forName("com.example.study.Person");
    Log.e("獲取全部構造函數","——————————");
    Constructor<Person>[] constructors = (Constructor<Person>[]) c.getConstructors();
    for (Constructor<Person> constructor : constructors){
        Log.e("構造函數:", "" + constructor);
    }
} catch (Exception e) {// 爲了方便我就直接寫Exception了
    e.printStackTrace();
}

輸出結果:
在這裏插入圖片描述
我們通過getConstructors可以獲取到類的全部構造函數,那如何獲取指定構造?

//通過getDeclaredConstructor方法 傳入想獲取的構造函數對應的參數類型即可
//這裏不再需要封裝類型,基礎類型直接 .class 即可
Constructor<Person> constructor = c.getDeclaredConstructor(String.class, int.class);
Log.e("獲取指定構造函數", "——————————");
Log.e("構造函數:", "" + constructor);

輸出結果:
在這裏插入圖片描述
現在已經可以獲取構造方法,那麼該如何調用呢?

Constructor<Person> constructor = c.getDeclaredConstructor(String.class, int.class);
Log.e("獲取指定構造函數", "——————————");
Log.e("構造函數:", "" + constructor);
Person person = constructor.newInstance("Shy", 23);
Log.e("調用構造函數之後","name = " + person.getName());
Log.e("調用構造函數之後","age = " + person.getAge());

輸出結果:
在這裏插入圖片描述

通過反射獲取方法

獲取方法和獲取構造方法大體上思路是一樣的,就是個別方法會有所不同,同樣需要先獲取Class對象,通過Class對象獲取Method

Class<Person> c = (Class<Person>) Class.forName("com.example.study.Person");
Log.e("獲取全部方法","不包括私有方法!!!");
Method[] methods = c.getMethods();
for (Method method : methods){
    Log.e("方法:", "" + method);
}

輸出結果:
在這裏插入圖片描述
通過上面的代碼,會發現Person類中printAll()方法並沒有被打印出來,這就說明getMethods()能獲取除私有方法外的所有方法,那麼如何獲取私有方法呢?

Class<Person> c = (Class<Person>) Class.forName("com.example.study.Person");
Log.e("獲取當前類的方法","不包括父類方法!!!");
Method[] methods = c.getDeclaredMethods();
for (Method method : methods){
    Log.e("方法:", "" + method);
}

輸出結果:
在這裏插入圖片描述
通過getDeclaredMethods()方法能夠獲取當前類的所有方法,包括私有方法,但是父類的方法無法獲取
當然,和獲取構造方法同樣,也可以根據參數獲取指定方法,調用getDeclaredMethod(name, paramType)方法可以獲取指定方法。

//需要傳入 參數名 參數類型, 如果沒有參數可以省去
Method method = c.getDeclaredMethod("printAll");
Log.e("獲取指定方法", "如果有參數,後面需要傳入對應的參數類型");
Log.e("指定方法:", "" + method);

在這裏插入圖片描述
最後來說一下如何調用獲取到的方法,再Method類中,有一個invoke方法,即爲調用方法。

Method method = c.getDeclaredMethod("printAll");
Log.e("獲取指定方法", "如果有參數,後面需要傳入對應的參數類型");
Log.e("指定方法:", "" + method);
//要執行方法的對象
Object obj = c.newInstance();
//這裏要注意下,如果被調用方法是私有方法必須調用setAccessible設爲true!!!
method.setAccessible(true);
//如果調用的方法有參數,後面要跟上參數值
method.invoke(obj);

輸出結果:
在這裏插入圖片描述

通過反射獲取屬性

屬性的獲取、調用和方法的很相似就不過多贅述了直接上代碼
通過getDeclaredFields獲得當前類的全部屬性,包括私有屬性,但不包括父類屬性

Log.e("獲取當前類屬性","不包括父類的屬性!!!");
Field[] fields = c.getDeclaredFields();
for(Field field : fields){
    Log.e("屬性:", "" + field);
}

輸出結果:
在這裏插入圖片描述
獲取指定屬性:

Log.e("獲取指定屬性","獲取指定屬性");
Field field = c.getDeclaredField("name");
Person person = new Person("Sun", 23);
Log.e("獲取指定屬性name:","" + field.get(person));

輸出結果:
在這裏插入圖片描述
修改指定屬性:

Log.e("獲取指定屬性","獲取指定屬性");
Field field = c.getDeclaredField("name");
Person person = new Person("Sun", 23);
Log.e("獲取指定屬性name:","" + field.get(person));
Log.e("修改指定屬性","私有屬性需要調用setAccessible並且設爲true");
//如果屬性是私有屬性 也需要調用setAccessible(true)
//field.setAccessible(true);
field.set(person, "Shy");
Log.e("修改指定屬性name:","" + field.get(person));

輸出結果:
在這裏插入圖片描述

靜態代理模式

想象一個場景:有一家男裝工廠ManFactory生產男裝,一家男裝商城ManShop售賣男裝;張三想從這家男裝商城購買一件黑色的男裝,但是距離太遠他沒法去,只能通過代購Person去買,代購還可以給他提供售前售後服務。
根據這樣的場景,我們編寫一下基礎代碼:
男裝工廠 ManFactory

//男裝工廠  生產男裝
public interface ManFactory {
    public void buyManClothes(String color);
}

男裝商城 ManShop

//男裝商城售賣男裝
public class ManShop implements ManFactory {
	//商場買衣服事件
    @Override
    public void buyManClothes(String color) { 
        Log.e("男裝商城ManShop","買了一件 "+ color +" 男裝");
    }
}

代購人Person

//代購Person 買衣服可以提供售前 售後服務
public class Person implements ManFactory {

    ManFactory manFactory; //必須持有真實對象

    public Person(ManFactory manFactory){
        this.manFactory = manFactory;
    }

    /** 售前服務 前置處理器 **/
    public void before(){
        Log.e("代購Person","售前服務");
    }

    /** 售後服務 後置處理器 **/
    public void after(){
        Log.e("代購Person","售後服務");
    }

	//代購買衣服事件
    @Override
    public void buyManClothes(String color) {
    	//售前服務
        before();
        //代購去商場買衣服
        manFactory.buyManClothes(color);
        //售後服務
        after();
    }
}

當張三找代購去購買男裝商場的男裝時

//張三告訴代購 去哪個商場買
ManFactory manFactory = new ManShop();
Person person = new Person(manFactory);
//代購根據張三的要求 去買 並且提供 售前售後服務
person.buyManClothes("黑色");

輸出結果:
在這裏插入圖片描述
以上的代碼就完成了簡單的靜態代理,靜態代理的缺點在於,如果此時,李四想要從女裝商場買一件女裝,他也去找Person代購,這樣一來,我們還得去修改Person,讓Person去實現WomanFactory,如果男裝商場購買條件不是顏色了,改爲Int類型的碼數了,那改動量就大了。所以靜態代理的缺點顯而易見,維護性差,可拓展性差。

動態代理模式

爲了解決靜態代理的種種缺點,動態代理出現了。接着上面的需求,李四又要買女裝;我們先創建女裝對應的工廠和商場
女裝工廠 WomanFactory

//女裝工廠 
public interface WomanFactory {
    public void buyWomanClothes(String color);
}

女裝商場 WomanShop

public class WomanShop implements WomanFactory {
    @Override
    public void buyWomanClothes(String color) {
        Log.e("女裝商城WomanShop","買了一件 "+ color +" 女裝");
    }
}

顯而易見,如果還去找Person代購,是滿足不了需求的,這時就出現了一家代購公司,可以代購多個商場的衣服。
代購公司 Company

public class Company implements InvocationHandler {

    Object shop; //持有的真實對象

    public Object getShop() {
        return shop;
    }

    public void setShop(Object shop) {
        this.shop = shop    ;
    }

    public Object getPoxyInstance(){
    	//newProxyInstance接受三個參數
    	//1. 要代理對象的類加載器
    	//2. 要代理對象的類實現的接口(根據反射得到,反射中沒有說獲取接口的方式,根據反射獲取方法舉一反三)
    	//3. 被代理對象要進行的操作(重寫的invoke方法)
        return Proxy.newProxyInstance(shop.getClass().getClassLoader(),
                shop.getClass().getInterfaces(),
                this);
    }

    /** 售前服務 前置處理器 **/
    public void before(){
        Log.e("代購公司","售前服務");
    }

    /** 售後服務 後置處理器 **/
    public void after(){
        Log.e("代購公司","售後服務");
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //售前服務
        before();
        //去代購 去商場買衣服
        Object proxyResult = method.invoke(shop,args);
        //售後服務
        after();
        return proxyResult;
    }
}

那麼此時,張三李四同時去找代購公司去買衣服用如下代碼表示:

//張三要買黑色男裝
//告訴代購公司 買哪個商場的衣服
ManFactory manFactory = new ManShop();
Company company = new Company();
company.setShop(manFactory);
//代購公司 派員工 person1 去代購
ManFactory person1 = (ManFactory) company.getPoxyInstance();
//員工 person1 根據張三的要求去買男裝 並且完成售前售後服務
person1.buyManClothes("黑色");
//李四 要買紅色女裝
//告訴代購公司 買哪個商場的衣服
WomanFactory womanFactory = new WomanShop();
company.setShop(womanFactory);
//代購公司 派員工 person2 去代購
WomanFactory person2 = (WomanFactory) company.getPoxyInstance();
//員工 person2 根據李四的要求去買女裝 並且完成售前售後服務
person2.buyWomanClothes("紅色");

輸出結果:
在這裏插入圖片描述
總結一下:學習反射和動態代理模式,更多的在於能夠讀懂一些主流的框架,很多框架都用動態代理封裝了一些操作供我們使用,比如:Retrofit,Retorfit就是將我們定義的網絡請求接口通過動態代理翻譯成網絡請求,再通過okhttp去請求。

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