反射機制與系統耦合實例詳解

反映射技術(以下簡稱:反射)的概念是由Smith在1982年首次提出的,主要是指程序可以訪問、檢測和修改它本身狀態或行爲的一種能力。這一概念的提出很快引發了計算機科學領域關於應用反射性的研究。它首先被程序語言的設計領域所採用。最近,反射機制也被應用到了視窗系統、操作系統和文件系統中。
在如今程序語言的設計領域中,幾乎每種OO語言都專門設計了支持反射技術的API,不管是Microsoft公司的.Net框架還是SUN公司的Java語言都是如此,本技術帖就以Java爲例來進行探討。
在Java編程語言中,反射是一種強大的工具。它使您能夠創建靈活的代碼,這些代碼可以在運行時裝配,無需在組件之間進行源代碼鏈接。這樣一來整個系統的耦合性就會降低並可以大大增加系統的靈活度。反射機制被大量運用在系統架構的設計層次上,並且在編寫公共類和系統基盤的時候也起到了舉足輕重的作用,甚至有人提出這門技術是一個程序員轉型成爲系統架構師的必經之路。
反射機制是 Java 被視爲動態(或準動態)語言的關鍵,允許程序於執行期取得任何已知名稱之 class 的內部信息,包括 包、父類、接口、內部類、屬性、結構體、方法,並可於執行期生成實體、變更 字段內容或喚起 方法。
有了反射機制,我們可以:
1.判斷某個對象所屬的類型(Class)。
2.取得類型(Class)的屬性,方法,構造體和父類的相關信息。
3.找出接口中的常量和方法定義。
4.爲一個執行期才得知名稱的類產生對象。
 
Java類反射中的主要方法:
對於類而言構造函數、字段和方法是最爲重要的內容java.lang.Class 提供四種獨立的反射調用,以不同的方式來獲得信息。調用都遵循一種標準格式。
以下是用於查找構造函數的一組反射調用:
方法
說明
Constructor getConstructor(Class[] params)
獲得使用特殊的參數類型的
公共構造函數
Constructor[] getConstructors()
獲得類的所有公共構造函數
Constructor getDeclaredConstructor(Class[] params)
獲得使用特定參數類型的構造函數(與訪問級別無關)
Constructor[] getDeclaredConstructors()
獲得類的所有構造函數(與訪問級別無關)
 
獲得字段信息的Class 反射調用不同於那些用於接入構造函數的調用,在參數類型數組中使用了字段名:
方法
說明
Field getField(String name)
獲得指定的公共字段
Field[] getFields()
獲得類的所有公共字段
Field getDeclaredField(String name)
獲得指定的字段
Field[] getDeclaredFields()
獲得類聲明的所有字段
 
用於獲得方法信息函數:
方法
說明
Method getMethod(String name, Class[] params)
使用特定的參數類型,獲得命名的公共方法
Method[] getMethods()
獲得類的所有公共方法
Method getDeclaredMethod(String name, Class[] params)
使用特定的參數類型,獲得類聲明的命名的方法
Method[] getDeclaredMethods()
獲得類聲明的所有方法
 
開始使用反射機制:
用於反射機制的類,如 Method,可以在 java.lang.relfect 包中找到。使用這些類的時候必須要遵循三個步驟:第一步是獲得你想操作的類的java.lang.Class 對象。在運行中的 Java 程序中,用 java.lang.Class 類來描述類和接口等。
下面就是獲得一個 Class 對象的方法之一:
Class c = Class.forName ("java.lang.Integer");
這條語句得到一個 String 類的類對象。還有另一種方法,如下面的語句:
Class c = Integer.class;
或者
Class c = Integer.TYPE;
它們可獲得基本類型的類信息。其中後一種方法中訪問的是基本類型的封裝類 ( Integer) 中預先定義好的 TYPE 字段。
第二步是調用諸如 getDeclaredMethods 的方法,以取得該類中定義的所有方法的列表。
一旦取得這個信息,就可以進行第三步了。
第三步是使用 reflection API 來操作這些信息,如下面這段代碼:

Class c = Class.forName("java.lang.String");
Method m[] = c.getDeclaredMethods();
System.out.println(m[0].toString());
 
 
 
 
 

它將以文本方式打印出 String 中定義的第一個方法的原型。
反射經常由框架型代碼使用,由於這一點,我們可能希望框架能夠全面接入代碼,無需考慮常規的接入限制。例如當代碼在不值得信任的代碼共享的環境中運行時。
假設有以下這個類的聲明:

class DemoReflection
{
       private String name = null;
 
       private void doPrint() {
              System.out.println("print.....");
       }
};
可以肯定的是,這個類中的屬性name和方法doPrint都是無法對外展示的,但是使用了反射以後就可以辦到。

package cn.softworks.demo;
 
import java.lang.reflect.*;
 
publicclass TestReflection {
    publicstaticvoid main(String args[]) {
 
        try {
            // 通過反映射技術得到DemoReflection的類型
            Class cls = Class.forName("DemoReflection");
            // 動態創建DemoReflection類的實力
            Object instance = cls.newInstance();
            // 通過反映射技術得到DemoReflection的非公有方法doPrint
            Method m = cls.getDeclaredMethod("doPrint",
                    new Class[] { String.class });
            // 表示可以隨意訪問該類中的方法
            m.setAccessible(true);
            // 調用doPrint方法
            m.invoke(instance, new Object[] { "Softworks" });
        } catch (Exception ex) {
            System.out.println(ex);
        }
 
    }
};
 
在該代碼中,讀者可能看到了一個比較陌生的方法setAccessible,這個方法非常重要,如果它不被設置成true那麼所有非公有方法仍然無法調用,所以在調用非公有方法的時候需要注意這點。
Private屬性的訪問方式和方法的訪問方式類似。
反射的實際應用
       在之前的介紹中,我們已經瞭解了反射機制的重要性,也已經瞭解到了反射經常被運用到系統框架設計和系統解耦中,現在就以一個真實的項目開發案例來探討一下反射機制的重要性。
       1.什麼叫系統耦合:
       或許很少有讀者會在電影結束後做在電影院中觀看電影的幕後工作者,但是可以肯定的是,如果沒有那麼多幕後工作者,那麼就不會有諸如《指環王》等大片的出現了。但是如果真的要拍攝出像《指環王》這樣的大片,光依靠強大的拍攝團隊還不夠,團隊中的每個成員都必須要相互合作和交流,那麼這種行爲就被稱爲團隊人員和團隊人員的耦合關係。
       軟件系統就如同剛纔的電影拍攝團隊,是有大量的類(工作人員)所組成的,那麼這些類之間如果沒有交互的話,整個軟件系統就不可能正確合理的工作,因此軟件系統中的耦合就來自於類與類之間的通訊,例如以下代碼:

Cleaner clearner = new Cleaner(“Chen.yu”);
Broom broom = new Broom();
cleaner.clear(broom);
 
 
    通過這段代碼,我們可以看出,Cleaner類和Broom之間有相互交互的關係,也就是說這兩個類之間產生了耦合關係。請設想,代碼如果有一個人來開發的話,耦合關係是不會影響到系統開發的。因爲一個開發員可以理所當然的先開發Broom類再開發Clear類。可以軟件工程是一個複雜的過程,一個人是不可能完成所有開發任務的,現在假設我們系統中的Cleaner類和Broom類一定要有兩個人分別開發的話,那麼問題就暴露出來了。因爲根據代碼可知,要想開發Cleaner類就必須先開發Broom類,Cleaner類中的方法clear需要使用Broom類的信息。那麼究竟應該如何解決這個問題呢?      
       2.利用工廠模式解決耦合關係:
現在我把代碼改寫成以下的狀態:

  

package cn.softworks.test;
 
import cn.softworks.demo.BeanFactory;
import cn.softworks.demo.Cleaner;
import cn.softworks.demo.IClearEquipment;
 
/**
 *反射機制的測試類
 *
 *@version1.0
 *
 *@authorChen.yu
 *
 *上海Softworks對日軟件人才培訓中心版權所有
 */
publicclass TestClient {
   
    publicstaticvoid main(String args[]) {
       
        //通過工廠創建指定的清潔工具類
        IClearEquipment eq =
            (IClearEquipment) BeanFactory.newInstance().creator("ClearEquipment");
       
        if(eq == null)
            return;
       
        //創建清潔工對象
        Cleaner cleaner = new Cleaner();
       
        //清潔工人開始清潔
        cleaner.clear(eq);
    }
}
 
 代碼雖然多了,但是可以肯定的是在代碼中我們只使用了Cleaner類,並沒有使用到Broom類,那麼Broom類上哪裏去了呢?我們看到了IClearEquipment接口,Broom類正好實現了該接口,並且我們利用工廠模式BeanFactory完全隱藏了Broom類的創建細節,以此來解決Broom類與Cleaner類之間的耦合關係,現在Broom類和Cleaner類就可以交給2個開發員並行開發了。

以下是IClearEquipment接口的代碼片斷:

package cn.softworks.demo;
 
/**
 *
 *該接口是的作用是用來定義清潔設備的標準<br>
 *換句話說,如果要想成爲清潔設備,那麼就必須要具有清潔能力
 *
 *@version1.0
 *
 *@authorChen.yu
 *
 *上海Softworks對日軟件人才培訓中心版權所有
 */
publicinterface IClearEquipment {
    /**
     *清潔設備的清潔方法<br>
     *不同的清潔設備有不同的清潔方法
     *
     */
    publicvoid clear();
 
}
 
  

以下是BeanFactory類的代碼片斷:

package cn.softworks.demo;
 
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
 
/**
 *該類的作用是從配置文件中讀取類名,並依靠反射將指定類的實體<br>
 *返回,以此達到“清潔工”類和“清潔設備”類之間的解耦<br>
 *
 *該類被設置成了單例模式,並在創建指定類的時候加入了同步鎖,<br>
 *以此保證線程安全。
 *
 *@version1.0
 *
 *@authorChen.yu
 *
 *上海Softworks對日軟件人才培訓中心版權所有
 */
publicclass BeanFactory {
   
    /**
     *單例工廠實體
     */
    privatestatic BeanFactory instance = null;
   
    /**
     *用於保存softworks.cfg.properties配置文件的實體。
     */
    privatestatic Properties config = null;
   
    /**
     *默認配置文件路徑
     */
    privatestaticfinal String CONFIG_PATH = "softworks.cfg.properties";
 

  

    /**
     *使用了單例模式的工廠類默認構造函數
     *
     */
    private BeanFactory() {
       
        //得到配置文件的路徑
        InputStream stream = Thread.currentThread().getContextClassLoader()
                .getResourceAsStream(CONFIG_PATH);
 
        config = new Properties();
 
        try {
            //將配置文件信息加載到config對象中
            config.load(stream);
        } catch (IOException e) {
            instance = null;
        }
 
    }
   
    /**
     *創建BeanFactory實體的靜態方法
     *
     *@returnBeanFactory的單例實體
     */
    publicsynchronizedstatic BeanFactory newInstance() {
        //判斷BeanFactory的實體是否已經存在
        if (instance != null)
            returninstance;
       
        //如果BeanFactory實體不存在那麼立刻創建
        instance = new BeanFactory();
 
        returninstance;
 
    }

    

   
    /**
     *工廠了的創建方法,該方法可以用於創建配置文件中指定key名的類<br>
     *現在配置文件中有如下信息:<br>
     *ClearEquipment=cn.softworks.demo.DustCollector<br>
     *那麼當參數被設置成ClearEquipment的時候,通過該方法就會創建<br>
     *cn.softworks.demo.DustCollector類的實體。
     *
     *@paramkey 配置文件中類所對應的Key名
     *
     *@return     被加載類的實體
     */
    publicsynchronized Object creator(String key) {
       
        if(config == null)
            returnnull;
       
        //得到配置文件中的類名
        String className = config.getProperty(key);
        try {
            //通過反射機制創建類實體
            return Class.forName(className).newInstance();
 
        } catch (Exception e) {
 
            returnnull;
        }
    }
 
}
 
 
 

以下是Broom類的代碼片斷:

package cn.softworks.demo;
 
/**
 *該類是用來描述掃帚這個清潔工具的<br>
 *它實現了清潔工具接口,所以必須實現清潔方法
*@version1.0
*@authorChen.yu
 *上海Softworks對日軟件人才培訓中心版權所有
 */
publicclass Broom implements IClearEquipment {
    /**
     *掃帚的清潔方法
     */
    publicvoid clear() {
        System.out.println("The Cleaner Use Broom");
    }
}
 
 
以下是DustCollector類的代碼片斷:
 

package cn.softworks.demo;
 
/**
 *該類是用來描述吸塵器這個清潔工具的<br>
 *它實現了清潔工具接口,所以必須實現清潔方法
 *@version1.0
 *@authorChen.yu
 *上海Softworks對日軟件人才培訓中心版權所有
 */
publicclass DustCollector implements IClearEquipment {
   
    /**
     *掃帚的清潔方法
     */
    publicvoid clear() {
       
        System.out.println("The Cleaner Use Dust Collector");
       
    }
}
 
 

以下是Cleaner類的代碼片斷:

package cn.softworks.demo;
 
/**
 *該類是用來描述一個清潔工人的<br>
 *清潔工人會使用清潔設備來進行清潔工作的
 *
 *@version1.0
 *
 *@authorChen.yu
 *
 *上海Softworks對日軟件人才培訓中心版權所有
 */
publicclass Cleaner {
   
    /**
     *這個方法的作用是定義清潔工人的清潔行爲<br>
     *可以肯定的是,清潔工人必須藉助清潔設備才能清潔
     *
     *@parameq使用的清潔設備
     */
    publicvoid clear(IClearEquipment eq) {
       
        //清潔工人使用清潔設備進行清潔
        eq.clear();
       
    }
 
}
 
 
softworks.cfg.properties配置文件:
 ClearEquipment=cn.softworks.demo.DustCollector
 
 
代碼工作原理:
通過以上代碼可以知道,現在有一個清潔工類(Cleaner),兩個清潔設備類(DustCollector和Broom他們都實現了清潔設備接口(IClearEquipment)),清潔工人的清潔方法(clear)明確要求指明,清潔工人究竟使用哪個清潔設備來開展工作,爲了增加系統的靈活性,我們把清潔工人所需要使用的清潔設備在“softworks.cfg.properties”配置文件中做出了定義,並且利用BeanFactory從配置文件中讀出了清潔設備的名字,利用反射機制將該清潔設備傳入到了Cleaner類的clear方法中。這樣一來系統的靈活讀就上升了。如果系統需要更改清潔設備,那麼我們只需要更改配置文件中的類名就可以了,整個系統的代碼都不需要變更,因此反射機制不但解決了系統耦合,而且還大大增加了系統的靈活性。
       但是讀者可能要問這樣,那麼CleanerBeanFactory之間的耦合性問題又顯現出來了,這個問題又如何解決呢?
       以上這個問題我們可以利用Spring框架中的IoC(依賴注射)來解決。待續......
 
發佈了15 篇原創文章 · 獲贊 2 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章