07 Java反射/動態代理

筆者仿照第5篇Java IO的筆記方式,主要通過小程序來總結反射和動態代理的有關知識。

1. 反射

1.1 getClass()方法和getName()方法

public class Test
{
    public static void main(String[] args)
    {
        Temp tmp = new Temp();
        //getClass()方法返回tmp對象的運行時類,用Class類對象表示,getName()提取類名
        System.out.println(tmp.getClass().getName());
    }
}

class Temp
{
}
//輸出:
//Temp
getClass()方法屬於Object類中的public final類型的方法,所以不可重寫。

1.2 三種獲取類對象的方式

public class Test
{
    public static void main(String[] args) throws Exception
    {
        //聲明三個類的類的引用
        Class<?> tmp1 = null;
        Class<?> tmp2 = null;
        Class<?> tmp3 = null;
        //根據字符串:返回名字爲字符串的類的類對象,靜態方法
        tmp1 = Class.forName("Temp");
        //根據對象:返回對象所屬類的類對象
        tmp2 = new Temp().getClass();
        //根據類:返回類的類對象
        tmp3 = Temp.class;
        System.out.println(tmp1.getName());
        System.out.println(tmp2.getName());
        System.out.println(tmp3.getName());
    }
}

class Temp
{
}
//輸出
//Temp
//Temp
//Temp
獲取類對象時,最好用Class.forName()方法,因爲這時不必生成Temp類的對象,也可以將名字作爲參數,便於修改。

1.3 根據類對象得到其所表示的類的對象

1.3.1 通過類對象的newInstance()方法

public class Test
{
    public static void main(String[] args) throws Exception
    {
        //得到以字符串"Temp"爲名字的類的類對象
        Class<?> tmp = Class.forName("Temp");
        //newInstance()方法創建類對象所表示的類的對象
        //需要強制類型轉換,將返回的泛型對象轉換成類的對象
        Temp n = (Temp) tmp.newInstance();
        //對得到的對象進行操作
        n.set("Name");
        System.out.println(n);
    }
}

class Temp
{
    public String word;
    void set(String word)
    {
        this.word = word;
    }
    @Override
    public String toString()
    {
        return word;
    }
}
//輸出:
//Name
需要注意的一點是使用newInstance()方法生成的類的對象,一定要有無參構造函數,默認生成的和重載定義的皆可,否則會出現InstantiationException。

1.3.2 通過Constructor對象的newInstance()方法

import java.lang.reflect.*;
public class Test
{
    public static void main(String[] args) throws Exception
    {
        //獲取類對象
        Class<?> tmp = Class.forName("Temp");
        //類對象的getConstructor()方法返回tmp所表示的類的對象的public無參構造方法,用對象表示
        Constructor<?> cons = tmp.getConstructor();
        //類對象的getConstructors()方法返回tmp所表示的類的對象的所有public構造方法,用對象數組表示
        //數組中構造方法的順序與它們在類中出現的順序一致
        Constructor<?>[] consArr = tmp.getConstructors();
        //需要強制類型轉換,將返回的泛型對象轉換成類的對象,與newInstance()方法相似
        //無參構造方法生成新對象
        Temp n = (Temp) cons.newInstance();
        System.out.println(n);
        //使用有參構造函數
        //按照書寫順序選擇參數
        Temp m = (Temp) consArr[0].newInstance();
        Temp k = (Temp) consArr[1].newInstance("word");
        System.out.println(m);
        System.out.println(k);
    }
}

class Temp
{
    public String word;
    
    //顯式定義的無參構造函數
    //第一個出現的構造方法,對應於consArr[0]
    public Temp()
    {
    }
   //重載的有參構造函數
   //第二個出現的構造方法,對應於consArr[1]
    public Temp(String word)
    {
        this.word = word;
    } 
    @Override
    public String toString()
    {
        return word;
    }
}
//輸出:
//null
//null
//word
需要注意的是Constructor類屬於java.lang.reflect包,使用前需要導入該包。

1.4 根據類對象得到其所表示的類所實現的接口

import java.lang.reflect.*;
public class Test
{
    public static void main(String[] args) throws Exception
    {
        Class<?> tmp = Class.forName("Temp");
        //getInterface()方法提取tmp類對象所表示的類的所有接口到類對象數組
        Class<?>[] intf = tmp.getInterfaces();
        //得到所有接口的名字
        for(Class<?> interf : intf)
            System.out.println(interf.getName());
    }
}

interface INTF
{
    public String tag = "tag";
    public int number = 1;
    public void get();
}

class Temp implements INTF
{
    public String word;
    
    public Temp()
    {
    }

    public Temp(String word)
    {
        this.word = word;
    } 

    @Override
    public void get()
    {}

    @Override
    public String toString()
    {
        return word;
    }
}
//輸出:
//INTF
反射中類和接口都是用Class<?>類型的對象,也就是類對象表示的,因此都可以放入Class<?>[]類型的數組中。

1.5 根據類對象得到其所表示的類的父類

import java.lang.reflect.*;
public class Test
{
    public static void main(String[] args) throws Exception
    {
        Class<?> c = Class.forName("Child");
        //getSuperclass()方法通過子類的類對象得到父類的類對象
        Class<?> f = c.getSuperclass();
        System.out.println(f.getName());
    }
}

class Father{}

class Child extends Father{}
//輸出
//Father

1.6 根據類對象得到其所表示的類的構造方法

import java.lang.reflect.*;
public class Test
{
    public static void main(String[] args) throws Exception
    {
        Class<?> tmp = Class.forName("Temp");
        //根據類對象得到其所表示的類的構造方法的類對象數組
        Constructor<?>[] consArr = tmp.getConstructors();
        //遍歷數組輸出所有構造方法的簽名
        for(Constructor<?> c : consArr)
            System.out.println(c);
    }
}

class Temp
{
    public String word;

    public Temp()
    {}

    public Temp(String word)
    {
        this.word = word;
    }
}
//輸出:
//public Temp()
//public Temp(java.lang.String)
有兩點需要注意,一是getConstructor()和getConstructors()方法都只能找到public的構造方法,二是返回的構造方法的類對象中僅有訪問修飾符和函數簽名的信息,構造方法的方法體不包括在內。

1.7 根據類對象調用其所表示的類的方法

import java.lang.reflect.*;
public class Test
{
    public static void main(String[] args) throws Exception
    {
        Class<?> tmp = Class.forName("Temp");
        //Method類也屬於java.lang.reflect包,用來保存提取到的類方法或實例方法
        //getMethod()方法按字符串返回名稱相同的public方法的方法對象
        Method mtdGet = tmp.getMethod("get");
        //invoke()方法調用字符串內對象的方法對象表示的方法
        mtdGet.invoke(tmp.newInstance());
        //返回有參數的方法的方法對象
        Method mtdSet = tmp.getMethod("set", String.class);
        //調用方法對象表示的方法,帶參數
        mtdSet.invoke(tmp.newInstance(), "word");
    }
}

class Temp
{
    public String word;

    public Temp()
    {}

    public Temp(String word)
    {
        this.word = word;
    }

    public void set(String word)
    {
        this.word = word;
        System.out.println(word);
    }

    public void get()
    {
        System.out.println(word);
    }
}
//輸出
//null
//word
與getConstructor()相似,getMethod()只能返回public方法。與getConstructors()相似,getMethods()返回多個方法對象到方法對象數組中。

1.8 根據類對象調用其所表示的類的字段

import java.lang.reflect.*;
public class Test
{
    public static void main(String[] args) throws Exception
    {
        Class<?> tmp = Class.forName("Temp");
        //Field類也屬於java.lang.reflect包,用來保存提取到的類字段或實例字段
        //getField()方法按字符串返回名稱相同的public字段的字段對象
        Field fid = tmp.getField("word");
        //顯示字段對象
        System.out.println(fid);
        //生成實例
        Temp n = (Temp) tmp.newInstance();
        //顯示字段對象所表示字段的值
        System.out.println(fid.get(n));
        //設置字段對象所表示字段的值
        fid.set(n, "word");
        //顯示新值
        System.out.println(fid.get(n));
    }
}

class Temp
{
    public String word;

    public Temp()
    {}

    public Temp(String word)
    {
        this.word = word;
    }

    public void set(String word)
    {
        this.word = word;
        System.out.println(word);
    }

    public void get()
    {
        System.out.println(word);
    }
}
//輸出:
//public java.lang.String Temp.word
//null
//word
與getMethod()相似,getField()只能返回public方法。與getMethods()相似,getFields()返回多個字段對象到字段對象數組中。

1.9 getDeclaredConstructor(s), getDeclaredMethod(s), getDeclaredField(s)方法

筆者在上面強調了get...方法的適用範圍僅限public構造方法,方法和字段。那怎樣獲得類對象所表示的類的所有(包括private)的方法和字段呢?這時需要getDeclared...方法。get...方法和getDeclared...方法的另一個重要區別是是否包含繼承的字段/方法。
以getDeclaredMethods()方法爲例:
import java.lang.reflect.*;
public class Test
{
    public static void main(String[] args) throws Exception
    {
        Class<?> tmp = Class.forName("Temp");
        Method[] mtdArr = tmp.getMethods();
        //獲取所有public方法包括繼承的public方法和實現的接口的public方法
        for(Method mtd : mtdArr)
            System.out.println(mtd);
        System.out.println(" =  =  =  =  =  =  =  = ");
        //獲取public,protected,default和private方法,但不包括繼承的方法
        Method[] mtdArrDeclared = tmp.getDeclaredMethods();
        for(Method mtd: mtdArrDeclared)
            System.out.println(mtd);

    }
}

class Temp
{
    public String word;

    public Temp()
    {}

    public Temp(String word)
    {
        this.word = word;
    }

    public void set(String word)
    {
        this.word = word;
        System.out.println(word);
    }

    private void get()
    {
        System.out.println(word);
    }
}
//輸出:
//public void Temp.set(java.lang.String)
//public final native java.lang.Object.wait() throws java.lang.InterruptedException
//public final void java.lang.Object.wait(long, int) throws java.lang.InterruptedException
//public boolean java.lang.Object.equals(java.lang.Object)
//public java.lang.String java.lang.Object.toString()
//public native int java.lang.Object.hashCode()
//public final native java.lang.Class java.lang.Object.getClass()
//public final native void java.lang.Object.notify()
//public final native void java.lang.Object.notifyAll()
// =  =  =  =  =  =  = 
//private void Temp.get()
//public void Temp.set(java.lang.String)
容易看出getMethods()方法返回了Temp類自身的public方法和繼承自Object類的8個方法,而getDeclaredMethods()方法僅返回了Temp類自身的一個public方法和一個private方法。注意它們都沒有返回任何構造方法。另一個要注意的地方是數組元素的順序不一定與方法在代碼中出現的順序一致。

1.10 基本類型和數組的反射

前面討論的情形都是自定義的類或者接口,那java基本類型和數組能不能使用反射呢?
public class Test
{
    public static void main(String[] args) throws Exception
    {
        //Class類的isPrimitive()方法判斷類對象是否表示基本類型
        System.out.println(int.class.isPrimitive());
        //包裝類.TYPE是包裝類對應基本類型的類對象
        System.out.println(Integer.TYPE);
        //void像基本類型一樣擁有預定義類對象,其預定義類對象是Void.TYPE
        System.out.println(void.class.isPrimitive());
        System.out.println(Void.TYPE);
    }
}
//輸出:
//true
//int
//true
//void
由此可見java基本類型加上void類型都有各自的類對象。基本類型的類對象是包裝類.TYPE,void類型的類對象是Void.TYPE。基本類型存儲在棧中,存取速度更快。包裝類存儲在堆中,使用更靈活。因爲基本類型需要裝箱成包裝類才能具備面向對象特性,所以基本類型的反射就是包裝類的反射:
import java.lang.reflect.*;
public class Test
{
    public static void main(String[] args) throws Exception
    {
        Class<?> intTmp = Class.forName("java.lang.Integer");
        Method[] mtdArr = intTmp.getMethods();
        //這樣會顯示出Integer類的許多public方法
        for(Method mtd : mtdArr)
            System.out.println(mtd);
    }
}
數組也是類,也能獲得類對象。不同的是數組只能聲明得到,而不能用構造方法得到,所以newInstance()對於數組的類對象無效。但是對於數組的元素,它們應當是基本類型或者Object類型的,所以可以反射:
import java.lang.reflect.*;
public class Test
{
    public static void main(String[] args) throws Exception
    {
        int[] temp = {1, 2, 3};
        //獲取數組的類對象
        Class<?> tmp = temp.getClass();
        //顯示數組的類對象所表示的類的名字
        System.out.println(tmp.getName());
        //獲取數組元素的類對象
        Class<?> tmpElement = temp.getClass().getComponentType();
        //顯示數組元素的類對象所表示的類的名字
        System.out.println(tmpElement.getName());
    }
}
//輸出:
//[I 
//int
數組的類對象所表示的類寫作"[大寫字母"形式,括號表示數組,字母表示元素的類型。

1.11 獲取類加載器

類加載器是負責加載類的對象,是ClassLoader類的具體實現。
import java.lang.reflect.*;
public class Test
{
    public static void main(String[] args) throws Exception
    {
        Test t = new Test();
        //getClassLoader()方法被類對象調用,返回ClassLoader類的對象,
        //獲取這個對象的類對象就是Test類的類加載器的類對象
        System.out.println(t.getClass().getClassLoader().getClass().getName());
    }
}
//輸出:
//sun.misc.Launcher$AppClassLoader

2. 動態代理

2.1 代理模式

代理模式(Proxy Pattern)是java設計模式中的一種。當一個客戶不想或不能直接引用一個類的對象時,代理對象可以在客戶和它要引用的對象間起到中介的作用。代理模式的參與方有三個:
(1)客戶接口:一組接口,委託類和代理類都實現這組接口。客戶的作用是指示代理類的對象去操作委託類的對象;
(2)代理對象:代理類的對象,實現客戶接口,也擁有委託類的對象的引用。因爲代理對象和委託類的對象都實現客戶接口,所以代理對象可以代替委託類的對象來滿足客戶要求。也因爲代理對象擁有委託類的對象的引用,所以代理對象可以操作委託類的對象,比如調用委託類的方法。代理的作用是充當客戶和委託類的中介,接收來自客戶的要求,轉給委託類的對象實際執行;
(3)委託類對象:委託類實現客戶接口。委託類的對象接收代理對象轉發來的客戶要求,調用自己的方法來滿足客戶。

2.2 動態代理模式

根據上面的描述,代理要滿足客戶要求,必須實現客戶接口,才能接收客戶要求,必須擁有委託類的對象引用,才能將要求轉給委託類的對象實際執行。這樣的代理類非常不靈活,其實現的客戶接口和擁有的委託類的對象是固定的。如果需要更換客戶,或者用不同的委託類實際處理客戶要求,就需要重寫代理。將客戶接口和委託類的對象作爲構造參數傳入,就可以重複利用同一個代理類關聯不同的客戶接口和委託類了。
動態代理模式(Dynamic Proxy Pattern)就是指代理通過構造參數獲取客戶接口和委託類的對象。具體體現在代理類需要三個構造參數來關聯客戶和委託類,一個是委託類的類加載器(通過反射獲得),一個是客戶接口(通過反射獲得),一個是Handler對象(實現InvocationHandler接口的類的對象,實際處理代理轉發來的客戶要求,Handler對象通過構造參數獲取委託類的對象的引用)。這樣的設計可以提高代理類的可重用性。所謂動態就是代理在運行時才關聯客戶接口和委託類,這種關聯可以在運行時改變。下面是一個簡單的例子:
import java.lang.reflect.*;
public class Test
{
    public static void main(String[] args) throws Exception
    {
        Worker worker = new Worker();
        //java.lang.reflect.Proxy是代理類,Proxy.newProxyInstance()方法需要委託類的類加載器,委託類的一組接口和調用處理器三個參數來生成代理類的對象
        //newProxyInstance()是Proxy類的一個靜態方法,返回一個Object類型的代理類的對象轉換成接口賦值給客戶接口
        Client client = (Client) Proxy.newProxyInstance(worker.getClass().getClassLoader(), worker.getClass().getInterfaces(), new ClientHandler(worker));
        //接口中的方法調用代理類的對象中的invoke()方法,該方法利用委託類的類對象提取到的方法對象和方法參數來運行委託類的方法,並將委託類的方法運行結果返回給代理類
        client.plan("jobNext");
        client.solve("jobNow");
    }
}
//使用代理的方法都被包含在一個接口中,成爲客戶接口
interface Client
{
    public void plan(String content);
    public void solve(String content);
}
//委託類:實現了客戶接口中的方法,被代理類的對象委託來執行客戶接口的方法
class Worker implements Client 
{
    public void plan(String content)
    {
        System.out.println("Planning of " + content + " done.");
    }
    public void solve(String content)
    {
        System.out.println("Solution of " + content + " done.");
    }
}
//java.lang.reflect.InvocationHandler接口是代理類的對象的調用處理程序實現的接口
//客戶接口通知代理執行某方法,代理調用InvocationHandler接口中的處理程序,通常寫在invoke()方法中
class ClientHandler implements InvocationHandler
{
    private Object proxyObject;
    //需要委託類的對象proxyObject作爲構造參數,以便invoke()方法調用委託類的方法
    public ClientHandler(Object proxyObject)
    {
        this.proxyObject = proxyObject;
    }
    //invoke()方法處理代理類實例接到的所有方法調用,第一個參數是代理類實例,第二個參數是委託類被調用的方法的方法對象,第三個參數是委託類被調用的方法的參數
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        System.out.println("before working");
        Object rslt = method.invoke(proxyObject, args);
        System.out.println("after working");
        return rslt;
    }
}
//輸出:
//before working
//Planning of jobNext done.
//after working
//before working
//Solution of jobNow done.
//after working
上面的例子中Client是客戶接口。Client調用它的方法就是在向代理髮出要求。Proxy類是代理,代理通過構造參數獲得委託類的類加載器,客戶接口,和實際處理客戶要求的ClientHandler類的對象。ClientHandler類通過構造參數得到委託類Worker的一個對象,代理轉發客戶要求給ClientHandler類的對象,後者轉發給Worker類的對象來實際執行。相比於靜態代理模式的客戶->代理->
委託的處理順序,動態代理模式使用客戶->代理->Handler->委託的處理順序。動態代理不僅可以動態關聯客戶和委託,還可以再增加一層靈活性,動態關聯Handler(不同的方法來重寫Handler的invoke()方法,可以生成不同的Handler),相當於給委託類做不同的包裝。

2.3 動態代理的用途

設計模式是根據大量實踐總結出來的程序模板。不同的設計模式實質上是使用不同模板操作對象,以滿足不同的使用場合。代理模式也是用來滿足某些場合的:
(1)需要考慮不同的權限:不同用戶對同一對象的操作權限不同,相當於不同權限的同一客戶(同樣的方法調用但權限不同)要求該對象給出不同反應。這時可以用代理來判斷客戶權限,然後用不同方法操作對象;
(2)需要以簡代繁,防止阻塞:在網絡上訪問某個很大的資源(比如圖片),限於網速需要等待,會導致該資源後面需要訪問的資源都被阻塞。如果訪問順序無關緊要,可以使用代理來響應資源的訪問,使得後面的訪問不再等待。
(3)其它需要用不同方法包裝同一個對象,以便對於不同訪問給出不同反應的場合。此時代理的作用就是避免修改對象本身,而是修改中介。

3. 總結

反射和動態代理作爲Java高新技術很重要的部分,應該給予充分的重視。動態代理使用反射技術,可以在不修改委託類的情況下對委託類做一些靈活包裝,以適應不同的需求,同時也隱藏了委託類,相當於用設計模式實現了面向對象的隱藏和封裝原則。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章