DTO--數據傳輸對象

層間數據傳輸的過程就是服務的執行者將數據返回給服務的調用者的過程。在非分佈式系統中因爲有相似Open session in view這樣的「怪胎解決方案」的存在,因此層間數據傳輸的問題並無充分暴露出來,可是在分佈式系統中咱們就能清楚地意識到層間數據傳輸的問題,從而可以更合理的進行設計。爲了暴露更多問題,本章討論的層間數據傳輸假定的場景是「服務器將執行的數據結果如何傳遞給遠程客戶端」,儘管在實際場景中服務的提供者和服務的調用者有可能處於同一虛擬機中(好比Web端與應用服務部署在同一服務器中)。
java

 

Data Transfer Object(數據傳輸對象)

一個數據傳輸對象 (DTO),用該對象包含遠程調用所須要的全部數據。修改遠程方法簽名,以便將 DTO 做爲單個參數接受,並將單個 DTO 參數返回給客戶端。在調用方應用程序收到 DTO 並將其做爲本地對象存儲以後,應用程序能夠分別對 DTO 發出一系列單獨的過程調用,而不會引起遠程調用開銷。

優勢 減小了遠程調用次數。經過在單個遠程調用中傳輸更多的數據,應用程序能夠減小遠程調用次數。 提升了性能。遠程調用可使應用程序的運行速度大大下降。減小調用次數是提升性能的最佳方法之一。在大多數方案中,傳輸大量數據的遠程調用所用的時間與僅傳輸少許數據的調用所用的時間幾乎相等。 隱藏內部狀況。在單個調用中來回傳遞更多的數據,還能夠更有效地將遠程應用程序的內部狀況隱藏在粗粒度接口的背後。這就是使用 Remote Facade 模式 [Fowler03] 的主要緣由。 發現業務對象。在一些狀況下,定義 DTO 有助於發現有意義的業務對象。在建立用做 DTO 的自定義類時,您一般會注意到做爲一組凝聚性信息而顯示給用戶或另外一個系統的元素分組。一般,這些分組用做描述應用程序所處理的業務域的對象的有用原型。 可測試性。將全部參數封裝到可序列化對象中能夠提升可測試性。例如,能夠從 XML 文件中讀取 DTO,並調用遠程函數以測試它們。一樣,能夠輕鬆地將結果再序列化爲 XML 格式,並將 XML 文檔與所需結果進行比較,而沒必要建立冗長的比較腳本。 缺點 可能須要太多的類。若是選擇了使用強類型的 DTO,則可能必須爲每一個遠程方法建立一個(若是考慮返回值,則爲兩個)DTO。即便在粗粒度接口中,這也可能致使大量的類。編寫如此數量的類的代碼並管理這些類會是很困難的。使用自動代碼生成能夠在必定程度上緩解此問題。 增長計算量。若是將服務器上的一種數據格式轉換爲能夠跨網絡傳輸的字節流,並在客戶端應用程序內轉換回對象格式,能夠帶來至關大的開銷。一般,須要未來自多個源的數據聚合到服務器上的單個 DTO 中。要提升經過網絡進行遠程調用的效率,必須在任一端執行其餘計算,才能聚合和串行化信息。 增長編碼工做量。能夠用一行代碼完成將參數傳遞到方法的操做。使用 DTO 要求實例化新對象,併爲每一個參數調用 setters 和 getters。編寫此代碼多是很乏味的。sql

 

10.1  什麼是DTO

在分佈式系統中,客戶端和服務器端交互有兩種情形:第一個是客戶端從服務器端讀取數據;第二個是客戶端將自己的數據傳遞給服務器端。數據庫

當有客戶端要向服務器端傳輸大量數據的時候,能夠經過一個包含要傳輸的全部數據的方法調用來完成。這在小數據量的時候缺點並不明顯,可是若是要傳遞包含有大量信息的數據的時候,這將變得難以忍受。下面的方法是任何人看了都會懼怕的:編程

public void save(String id,String number,String name,int type,int height,數組

int width,BigDecimal weight,BigDecimal price,String description)sass

這種接口也是很是的脆弱,一旦須要添加或者刪除某個屬性,方法的簽名就要改變。安全

當客戶端要從服務器端取得大量數據的時候,可使用多個細粒度的對服務器端的調用來獲取數據。好比:服務器

ISomeInterface intf = RemoteService.getSomeInterface();網絡

System.out.println("您要查詢的商品的資料爲:");session

System.out.println("編號:"+intf.getNumber(id));

System.out.println("姓名:"+intf.getName(id));

System.out.println("類型:"+intf.getType(id));

System.out.println("高度:"+intf.getHeight(id));

System.out.println("寬度:"+intf.getWidth(id));

System.out.println("價格:"+intf.getPrice(id));

System.out.println("描述信息:"+intf.getDescription(id));

這種方式中每個get***方法都是一個對服務器的遠程調用,都須要對參數和返回值進行序列化和反序列化,並且服務器進行這些調用的時候還須要進行事務、權限、日誌的處理,這會形成性能的大幅降低。若是沒有使用客戶端事務的話還會致使這些調用不在一個事務中從而致使數據錯誤。

系統須要一種在客戶端和服務器端之間高效、安全地進行數據傳輸的技術。DTO(Data Transfer Object,數據傳送對象)是解決這個問題的比較好的方式。DTO是一個普通的Java類,它封裝了要傳送的批量的數據。當客戶端須要讀取服務器端的數據的時候,服務器端將數據封裝在DTO中,這樣客戶端就能夠在一個網絡調用中得到它須要的全部數據。

仍是上面的例子,服務器端的服務將建立一個DTO並封裝客戶端所須要的屬性,而後返回給客戶端:

ISomeInterface intf = RemoteService.getSomeInterface();

SomeDTOInfo info = intf.getSomeData(id);

System.out.println("您要查詢的商品的資料爲:");

System.out.println("編號:"+info.getNumber());

System.out.println("姓名:"+info.getName());

System.out.println("類型:"+info.getType());

System.out.println("高度:"+info.getHeight());

System.out.println("寬度:"+info.getWidth());

System.out.println("價格:"+info.getPrice());

System.out.println("描述信息:"+info.getDescription());

使用DTO 的時候,一個主要問題是選擇什麼樣的DTO:這個DTO可以容納哪些數據,DTO的結構是什麼,這個DTO是如何產生的。DTO是服務器端和客戶端進行通訊的一個協議格式,合理的DTO設計將會使得服務器和客戶端的通訊更加順暢。在水平開發模式(即每一個開發人員負責系統的不一樣層,A專門負責Web表現層的開發,B專門負責服務層的開發)中,在項目初期合理的DTO設計會減小各層開發人員之間的糾紛;在垂直開發模式(即每一個開發人員負責不一樣模塊的全部層,A 專門負責庫存管理模塊的開發,B專門負責固定資產模塊的開發)中,雖然開發人員能夠自由地調整DTO的結構,可是合理的DTO設計仍然會減小返工的可能性。

實現DTO 最簡單的方法是將服務端的域對象(好比Hibernate中的PO、EJB中的實體Bean)進行拷貝而後做爲DTO傳遞。採用域對象作DTO比較簡單和清晰,由於DTO與域模型一致,因此瞭解一個結構就夠了。這樣作也免去了DTO的設計,使得開發工做變得更快。這種作法的缺點是域DTO的粒度太大以致於難以知足客戶端的細粒度的要求,客戶端可能不須要訪問那些域中的全部屬性,也可能須要不是簡單地被封裝在域中的數據,當域DTO不能知足要求的時候就須要更加細粒度的DTO方案。目前主流的DTO解決方案有定製DTO、數據傳送哈希表、數據傳送行集。

10.2  域DTO

域模型是指從業務模型中抽取出來的對象模型,好比商品、倉庫。在J2EE中,最多見的域模型就是可持久化對象,好比Hibernate中的PO、EJB中的實體Bean。

在分佈式系統中,域模型徹底位於服務器端。根據持久化對象能否直接傳遞到客戶端,域對象能夠分爲兩種類型:一種是服務器端的持久化對象不能夠直接傳遞到客戶端,好比EJB中的實體Bean是不能被傳遞到客戶端的;一種是持久化對象能夠直接傳遞到客戶端,好比Hibernate中的PO變爲detached object之後就能夠傳遞到客戶端。

EJB中的實體Bean不能直接傳遞到客戶端,並且實體Bean不是一個簡單的JavaBean,因此也不能經過深度克隆(deep clone)創造一個新的可傳遞Bean的方式產生DTO。針對這種狀況,必須編寫一個簡單的JavaBean來做爲DTO。

下面是一個系統用戶的實體Bean的代碼:

abstract public class SystemUserBean implements EntityBean

{

    EntityContext entityContext;

    public java.lang.String ejbCreate(java.lang.String userId)

            throws CreateException

    {

        setUserId(userId);

        return null;

    }

    public void ejbPostCreate(java.lang.String userId)
            throws CreateException

    {      

    }

    public void ejbRemove() throws RemoveException

    {      

    }

    public abstract void setUserId(java.lang.String userId);

    public abstract void setName(java.lang.String name);

    public abstract void setPassword(java.lang.String password);

    public abstract void setRole(java.lang.Integer role);

    public abstract java.lang.String getUserId();

    public abstract java.lang.String getName();

    public abstract java.lang.String getPassword();

    public abstract java.lang.Integer getRole();

    public void ejbLoad()

    {      

    }

    public void ejbStore()

    {      

    }

    public void ejbActivate()

    {      

    }

    public void ejbPassivate()

    {      

    }

    public void unsetEntityContext()

    {

        this.entityContext = null;

    }

    public void setEntityContext(EntityContext entityContext)

    {

        this.entityContext = entityContext;

    }

}

根據須要咱們設計了以下的DTO:

public class SystemUserDto implements Serializable

{

    private String userId;

    private String name;

    private String password;

    private Integer role;

    public void setUserId(String userId)

    {

        this.userId = userId;

    }

    public String getUserId()

    {

        return userId;

    }

    public void setName(String name)

    {

        this.name = name;

    }

    public String getName()

    {

        return name;

    }

    public void setPassword(String password)

    {

        this.password = password;

    }

    public String getPassword()

    {

        return password;

    }

    public void setRole(Integer role)

    {

        this.role = role;

    }

    public Integer getRole()

    {

        return role;

    }

}

爲了實現DTO的生成,這裏還須要一個將實體Bean轉換爲一個DTO的工具,咱們稱其爲DTOAssembler:

public class SystemUserDtoAssembler

{

    public static SystemUserDto createDto(SystemUser systemUser)

    {

        SystemUserDto systemUserDto = new SystemUserDto();

        if (systemUser != null)

        {

            systemUserDto.setUserId(systemUser.getUserId());

            systemUserDto.setName(systemUser.getName());

            systemUserDto.setPassword(systemUser.getPassword());

            systemUserDto.setRole(systemUser.getRole());

        }

        return systemUserDto;

    }

    public static SystemUserDto[] createDtos(Collection systemUsers)

    {

        List list = new ArrayList();

        if (systemUsers != null)

        {

            Iterator iterator = systemUsers.iterator();

            while (iterator.hasNext())

            {

                list.add(createDto((SystemUser) iterator.next()));

            }

        }

        SystemUserDto[] returnArray = new SystemUserDto[list.size()];

        return (SystemUserDto[]) list.toArray(returnArray);

    }

}

爲一個實體Bean產生DTO是很是麻煩的事情,因此像JBuilder這樣的IDE都提供了根據實體Bean直接生成DTO類和DTOAssembler的代碼生成器。

相對於重量級的實體Bean來講,使用 Hibernate的開發人員則輕鬆多了,由於Hibernate中的PO就是一個普通的JavaBean對象,並且PO能夠隨時脫離Hibernate 被傳遞到客戶端,不用進行復雜的DTO和DTOAssembler的開發。不過缺點也是有的,當一個PO脫離Hibernate之後若是客戶端訪問其並無在服務器端加載的屬性的時候就會拋出惰性加載的異常,而若是對PO不採用惰性加載的話則會致使Hibernate將此PO直接或者間接關聯的對象都取出來的問題,在有的狀況下這是災難性的。在案例系統中是使用DTOGenerator的方式來解決這種問題的。

不管是哪一種方式,客戶端都不能直接訪問服務器端的域模型,可是客戶端卻但願能和域模型進行協做,所以須要一種機制來容許客戶端像操縱域模型同樣操做DTO,這樣客戶端能夠對DTO進行讀取、更新的操做,就好像對域模型作了一樣的操做同樣。客戶端對DTO進行新增、修改、刪除等操做,而後將修改後的DTO傳回服務器端由服務器對其進行處理。對於實體Bean 來說,若是要處理從客戶端傳遞過來的DTO,就必須編寫一個DTODisassembler來將DTO解析爲實體Bean:

public class SystemUserDtoDisassembler

{

    public static SystemUser fromDto(SystemUserDto aDto)

            throws ServiceLocatorException, CreateException,

            FinderException

    {

        SystemUser systemUser = null;

        ServiceLocator serviceLoc = ServiceLocator.getInstance();

        SystemUserHome systemUserHome = (SystemUserHome) serviceLoc

                .getEjbLocalHome("SystemUserHome");

        boolean bFind = false;

        try

        {

            systemUser = systemUserHome.findByPrimaryKey(aDto.getPkId());

            bFind = (systemUser != null);

        } catch (FinderException fe)

        {

            bFind = false;

        }

        if (bFind != true)

            systemUser = systemUserHome.create(aDto.getPkId());

        systemUser.setName(aDto.getName());

        systemUser.setPassword(aDto.getPassword());

        systemUser.setRole(aDto.getRole());

        return systemUser;

    }

}

Hibernate在這方面的處理就又比實體Bean簡單了,主要把從客戶端傳來的DTO從新歸入Hibernate的管理便可,惟一須要注意的就是版本問題。

(1)   使用域DTO會有以下好處:

l   域模型結構能夠在一次網絡調用中複製到客戶端,客戶端能夠讀取、更新這個DTO而不須要額外的網絡調用開銷,並且客戶端還能夠經過將更新後的DTO回傳到服務器端以更新數據。

l   易於實現快速開發。經過使用域DTO能夠直接將域模型在層間傳輸,減小了工做量,能夠快速地構建出一個應用。

(2)   但它也有以下的缺點:

l   將客戶端和服務器端域對象耦合在一塊兒。若是域模型變了,那麼相應的DTO也會改變,即便對於Hibernate這種PO、DTO一體的系統來講也會一樣致使客戶端的代碼要從新編譯或者修改。

l   不能很好地知足客戶端的要求。客戶端可能只須要域對象的20個屬性中的一兩個,採用域DTO則會將20個屬性都傳遞到客戶端,浪費了網絡資源。

l   更新域對象很煩瑣。客戶端對DTO可能作了不少更新或者很深層次的更新,要探查這些更新而後更新域對象是很麻煩的事情。


 

10.3  定製DTO

域DTO解決了在客戶端和服務器端之間傳遞大量數據的問題,可是客戶端每每須要更細粒度的數據訪問。

例如,一件商品可能有不少屬性:名稱、編碼、重量、型號、大小、顏色、生產日期、生產廠家、批次、保質期等。而客戶端只對其中一部分屬性有要求,若是將包含全部屬性的商品對象到客戶端的話,將會即浪費時間又浪費網絡帶寬,並對系統的性能有不一樣程度的影響。

咱們須要一種可定製的DTO,使它僅封裝客戶端須要的數據的任意組合,徹底與服務器端的域模型相分離。定製DTO與域DTO的區別就是它不映射到任何服務器端的域模型。

從上述的商品例子,設想客戶端只須要一些與產品質量有關的屬性,在這種狀況下,應該創造一個封裝了這些特定屬性的DTO並傳送給客戶端。這個DTO是商品屬性的一個子集:

public class GoodsCustomDTO implements Serializable

{

    private Date productDate;

    private Date expireDate;

    private String batchNumber;

    public GoodsCustomDTO(Date productDate, Date expireDate, String
            batchNumber)

    {

        super();

        this.productDate = productDate;

        this.expireDate = expireDate;

        this.batchNumber = batchNumber;

    }

    public String getBatchNumber()

    {

        return batchNumber;

    }

    public Date getExpireDate()

    {

        return expireDate;

    }

    public Date getProductDate()

    {

        return productDate;

    }

}

通常來講,若是客戶端須要n個屬性,那麼應該創造一個包含且僅包含這n個屬性的DTO。使用這種方法,域模型的細節被隱藏在服務器中。這樣開發人員把DTO僅當作普通的數據,而不是任何像PO那樣的服務端的業務數據。固然採用定製DTO系統中會有愈來愈多的DTO,因此不少開發者情願使用粗糙一些的DTO(即包含比須要的屬性多的屬性),而不是從新編寫一個新的DTO,只要是返回的冗餘數據不是太多,仍是能夠接受的。畢竟對於任何一種技術,都須要尋求一個兼顧方便和性能的折衷點。

定製DTO主要用於只讀操做,也就是DTO只能用來顯示,而不能接受改變。既然定製DTO對象僅僅是一個數據的集合,和任何服務端對象沒有必然的關係,那麼對定製DTO進行更新就是沒有意義的了。

定製DTO的缺點以下:

l   須要建立大量的DTO。使用定製DTO會爆炸式地產生大量的對象。

l   客戶端DTO的版本必須和服務器端的版本一致。因爲客戶端和服務器端都經過定製DTO通訊,因此一旦服務器端的DTO增長了字段,那麼客戶端的代碼也必須從新編譯,不然會產生類版本不一致的問題。

10.4  數據傳送哈希表

使用定製DTO能夠解決域DTO的數據冗餘等問題,可是咱們須要編寫大量的DTO以便返回給客戶端它們所須要的數據,可是仍然有對象驟增、代碼版本等問題。解決這一問題的方法就是使用數據傳送哈希表。

JDK中的哈希表(HashMap、HashTable等)提供了一種通用的、可序列化的、可容納任意數據集合的容器。若使用哈希表做爲DTO客戶端和服務器端代碼之間數據傳送載體的話,惟一的依賴關係就是置於鍵中用於表示屬性的命名。

好比:

ISomeInterface intf = RemoteService.getSomeInterface();

Map info = intf.getSomeData(id);

System.out.println("您要查詢的商品的資料爲:");

System.out.println("編號:"+info.get("Number"));

System.out.println("姓名:"+info.get("Name"));

System.out.println("類型:"+info.get("Type"));

System.out.println("高度:"+info.get("Height"));

System.out.println("寬度:"+info.get("Width"));

System.out.println("價格:"+info.get("Price"));

使用數據傳送哈希表而不是域DTO或者定製DTO意味着增長了額外的實現複雜性,由於客戶端須要知道做爲鍵的字符串,以便在哈希表中取得感興趣的屬性。

(1)   使用數據傳送哈希表來進行數據傳遞的好處在於:

l   有很好的可維護性。沒必要像定製DTO那樣須要額外的類和重複的邏輯,取而代之的是通用的哈希表訪問。

l   維護代價低。無須任何服務器端編程就能夠建立新的服務器端數據的視圖,這樣客戶端能夠動態地決定須要哪些數據。

(2)   固然它也是有缺點的:

l   須要服務器和客戶端就鍵的命名達成一個約定。

l   沒法使用強類型的編譯時檢查。當使用定製DTO或者域DTO的時候,傳遞給set的值或者從get方法獲得的值老是正確的,任何錯誤都能在編譯時被發現。而使用數據傳送哈希表時,屬性訪問的問題只有運行時才能發現,並且讀取數據的時候也要進行類型轉換,這使得系統性能下降。

l   須要對基本類型進行封裝。Java中的基本數據類型,好比int、double、boolean等不能保存在哈希表中,由於它們不是對象,因此在放入哈希表以前須要採用Wrapper類封裝,不過在JDK 1.5之後的版本中再也不存在此問題。

10.5  數據傳送行集

當開發報表或者開發大數據量的客戶端的時候,直接用JDBC訪問數據庫是更好的方式,可是如何將查詢結果傳遞給客戶端呢?最普通的解決方法是使用DTO。例如,用JDBC查詢每種商品的銷售總量:

select sum(saleBillDetail.FQty) as FTotalQty,saleBillDetail.FGoodsName,saleBillDetail.FGoodsNumber as FGoodsName from T_SaleBillDetail as saleBillDetail group by  saleBillDetail.FgoodsId

咱們能夠建立一個定製DTO來傳送這個查詢的結果集:

public class SomeDTO  implements Serializable

{

    private BigDecimal totalQty;

    private String goodsNumber;

    private String goodsName;

    public SomeDTO (BigDecimal totalQty,String goodsNumber,String goodsName)

    {

        super();

        this.totalQty = totalQty;

        this.goodsNumber = goodsNumber;

        this.goodsName = goodsName;

    }

    public BigDecimal getTotalQty

    {

        return totalQty;

    }

    public String getGoodsNumber()

    {

        return goodsNumber;

    }

    public String getGoodsName()

    {

        return goodsName;

    }

}

服務器會執行報表SQL語句獲得一個包含每種商品銷量的結果集,而後服務器將結果集填裝DTO,結果集中的每一行都被轉換成DTO並加入一個集合中,填裝完畢,這個DTO集合就被傳遞到客戶端供客戶端顯示報表用。

SQL查詢語句是變幻無窮的,所以對於每種不一樣的查詢結果都要建立不一樣的DTO。並且數據已經表示在結果集的數據表的行中,將數據轉換到一個對象集合中,而後在客戶端又將對象集合轉換回由行和列組成的數據表顯然是多餘的。使用行集將原始的SQL查詢結果從服務器端直接返回給客戶端是更好的作法。

javax.sql.RowSet是 java.sql.ResultSet的子接口,而且在JDBC 3.0中它被做爲核心接口取代ResultSet。使用RowSet能夠將結果集封裝並傳遞到客戶端,因爲RowSet是ResultSet的子接口,因此客戶端能夠像操縱結果集同樣對RowSet進行操做。這容許開發人員將查詢結果與數據庫相分離,這樣就無須手工將結果集轉換成DTO而後又在客戶端從新轉換爲表格形式。

要將行集傳遞到客戶端,那麼這種行集必須是非鏈接的行集,也就是行集無須保持與數據庫的鏈接,徹底能夠脫離數據庫環境。Sun提供了一個實現如此功能的緩衝行集(Cached RowSet),這個實如今Sun JDK 1.5之後的版本中是包含在安裝包中的,若是使用其餘公司的JDK或者Sun JDK 1.4,則須要單獨到Sun的網站上去下載對應的Jar包。

在商品銷售總量報表的例子中,能夠用行集得到查詢的整個結果集,並將其傳遞到客戶端。爲了建立這個行集,能夠在服務端編寫以下的代碼:

ps = conn.prepareStatement(sql);

ResultSet rs = ps.executeQuery();

RowSet crs = new CachedRowSet();

crs.populate(rs);

return crs;

這樣客戶端就能夠獲得這個RowSet了。

(1)   用行集做爲跨層數據傳輸的方法的好處是:

l   行集對全部查詢操做都提供了統一的接口。使用行集,全部的客戶端均可以使用相同的接口知足全部的數據查詢須要。當客戶端要訪問的數據發生改變時行集接口是不變的。

l   消除了無謂的轉換。行集能夠直接從SQL執行的結果集中建立,而不用從結果集轉換爲DTO,再由DTO轉換爲表格。

(2)   使用行集的缺點是:

l   客戶端必須知道查詢結果集中列的名字。若是查詢SQL是隱藏在服務器端的話,表名、表之間的關係等對客戶端是透明的,可是客戶端仍然須要知道結果集中列的名字,這樣才能得到相關的值。

l   直接跳過了域模型。這是一種非面向對象的方式,有悖於基本的J2EE架構。這和Delphi中的「ClientDataSet僞三層」、.Net中的 「WebService返回DataSet」同樣,當使用行集的時候並無反映出來任何業務的概念,它們只是一堆數據而已。Scott Hanselman說:「從WebService返回DataSet,是撒旦的產物,表明了世界上一切真正邪惡的東西」。採用行集使得客戶端與服務器端的域模型綁定得更加緊密,當須要對系統重構的時候增長了工做量。

l   沒法使用強類型的編譯檢查。客戶端必須調用行集上的getString、getBoolean、getBigDecimal等方法來獲取數據,而不是調用DTO上的getName,getNumber。這使得客戶端的開發容易出如今運行時才能發現的錯誤。

l   行集接口定義了能夠修改行集數據並與數據庫同步的機制,可是開發人員應該避免使用這種手段在客戶端更新數據。爲了從根本上杜絕這種狀況的發生。能夠編寫一個子集的行集實現類(或者簡單地封裝一個CachedRowSet實現)把全部的與數據更新相關的行集操做經過異常等方式屏蔽。

10.6  案例系統的層間數據傳輸

上面幾節比較了常見的層間數據傳輸模式,這些模式都有各自的優缺點,必須根據實際狀況選擇合適的模式,絕對不能生搬硬套、人云亦云。

考慮到系統架構的合理性,不少人都是強調避免將域對象直接傳遞到客戶端的,由於這樣服務端的域模型就暴露給了客戶端,形成客戶端與服務器端的高度耦合。當域模型修改的時候,就要形成客戶端代碼的修改或者從新編寫。建議從新創建一個定製DTO類來傳輸必要的數據,這樣DTO與域模型就能夠獨立變化。

在大部分業務系統中,不少狀況下DTO與域模型是沒法獨立變化的,好比客戶要求爲一個商品增長一個「跟貨員」的屬性,而且要能在客戶端顯示、編輯這個屬性。這種狀況下咱們能作到只修改域模型而不修改DTO 嗎?若是客戶想去掉「批次」屬性,那麼若是隻從域模型中去掉這個屬性的話,客戶端保留編輯這個屬性的控件還有什麼意義嗎?

在大部分業務系統的普通邏輯中客戶端界面一般反映的就是域模型,因此不必進行屏蔽,這樣作只能增長無謂的工做量,下降開發效率。案例系統中在大部分狀況下能夠直接將域模型當作DTO直接傳遞給客戶端,只有在特殊的邏輯中才採用其餘的層間數據傳輸模式。

前面提到對於EJB咱們只能編寫一個和實體Bean 含有相同屬性的JavaBean做爲DTO,而因爲Hibernate的強大功能,PO的狀態管理能夠脫離Session。問題的關鍵是咱們不能把一個脫了Session管理的PO直接傳遞到客戶端,由於若是不採起LazyLoad的話,咱們會把服務器端全部與此PO相關聯的對象都傳遞到客戶端,這是任何人都沒法忍受的。而若是採用LazyLoad的話如何取得客戶端要的全部數據呢?一個方法是在服務器端把客戶端須要的全部數據採用BeanUtils之類的工具一次性都裝載好,而後傳遞給客戶端:

PersonInfo p = intf.getPersonByPK(id);

BeanUtils.getProperty(p,"age");

BeanUtils.getProperty(p,"parent.name");

BeanUtils.getProperty(p,"parent.company.name");

return p;

採用LazyLoad之後,對象的類型實際上是域對象的子類,其中包含了CGLib、Hibernate爲實現LazyLoad而添加的代碼(也就是上邊的p實際上是相似於PersonInfo$CGLib$ Proxy的類型)。若是使用Hessian、Burlap等傳遞的話會致使序列化問題,由於它們沒有能力序列化如此複雜的對象;若是使用RMI、 HttpInvoker雖然能夠將對象傳遞到客戶端,可是因爲反序列化的須要,CGLib、Hibernate的包是須要安裝在客戶端的,並且客戶端的代碼中一旦訪問了沒有在服務端加載到的屬性就會發生「Session已關閉」的異常。那麼採用一種更合理的形式把PO傳遞給客戶端就成爲一個必須解決的問題。

10.7  DTO生成器

將PO通過必定形式的轉換,傳遞給客戶端,使得客戶端可以方便地使用傳過來的DTO,這就是DTO生成器要解決的問題。把問題具體分解,咱們發現DTO生成器的功能以下:

l   容許客戶端指定加載哪些屬性,這樣DTO生成器就只加載客戶端指定的屬性,其餘屬性不予以加載,這減少了網絡流量。

l   屏蔽CGLib、Hibernate等的影響,客戶端能夠把DTO當成一個沒有任何反作用的普通JavaBean使用。

l   容許客戶端將修改後的DTO傳遞迴服務器端進行更新。

採用簡單的對象克隆方法沒法獲得知足要求的DTO,由於克隆之後的對象仍然是和PO同樣的被代理對象。更好的解決方法就是從新生成一個與PO的原有類型(好比PersonInfo,而非 PersonInfo$CGLib$Proxy)一致的JavaBean做爲DTO,而後將客戶端須要的PO中的屬性賦值到DTO中。在複製過程當中,由於 PO以及關聯的對象的信息已經被LazyLoad破壞得亂七八糟了,因此咱們必需要經過一種機制知道對象的字段有哪些、字段的類型是什麼、字段是不是關聯對象、關聯的類型是什麼。瞭解這些信息的最好方式就是經過元數據,案例系統的元數據機制就能夠知足這個要求,並且Hibernate也有元數據機制能提供相似的信息,下面就分別介紹經過這兩種元數據機制實現DTO生成器的方法。

10.7.1  生成器接口定義

DTO生成器要容許用戶指定轉換哪些屬性,指定的屬性的粒度精確到關聯屬性。下面假定有以下的員工域模型:員工有本身的上司(manager)、部門(department)、電腦設備(computer),自己還有工號、姓名等屬性。類圖如圖10.1所示。

圖10.1  員工類圖

類圖中的兩個「0..*—1」的關聯關係分別表示:一個部門能夠有0到多個員工,一個員工只屬於一個部門;一臺電腦能夠被0到多個員工同時佔用,但一個員工必須有且只有一臺電腦(這個假設比較特殊)。

假如客戶端想得到員工的全部屬性、所屬部門、間接上級、間接上級的上級,那麼只要指定相似於下面的格式就能夠了:department、manager.manager、manager.managermanager。

【例10.1】定義一個Selectors。

定義一個Selectors類來表示這些格式,代碼以下:

// 關聯字段選擇器

package com.cownew.PIS.framework.common.db;

import java.io.Serializable;

import java.util.HashSet;

import java.util.Iterator;

import java.util.Set;

public class Selectors implements Serializable

{

    private Set set;   

    public Selectors()

    {

        set = new HashSet();

    }

    public Selectors(int capacity)

    {

        set = new HashSet(capacity);

    }

    public boolean add(String string)

    {

        return set.add(string);

    }

    public boolean remove(String string)

    {

        return set.remove(string);

    }

    public Iterator iterator()

    {

        return set.iterator();

    }

    public String toString()

    {

        return set.toString();

    }

   

    public Selectors generateSubSelectors(String property)

    {

        property = property+".";

        Selectors newSelector = new Selectors();

        Iterator it = this.iterator();

        while(it.hasNext())

        {

            String item = it.next().toString();

            if(item.startsWith(property))

            {

                String subItem = item.substring(property.length());

                newSelector.add(subItem);

            }

        }

        return newSelector;

    }

   

    public boolean contains(String property)

    {

        Iterator it = this.iterator();

        while(it.hasNext())

        {

            String item = it.next().toString();

            if (item.startsWith(property))

            {

                return true;

            }

        }

        return false;

    }  

}

調用add方法向Selectors中添加要取得的屬性,支持級聯方式,好比manager.department;調用generateSubSelectors方法產生以property爲根的新的 Selectors,好比Selectors中有manager.department、manager.manager、computer三項,調用 generateSub- Selectors("manager")之後就產生了department、manager兩項;調用contains判斷一個property屬性是否被定義在Seletors中了,好比Selectors中有manager.department、manager.manager、computer 三項,那麼調用contains("manager")返回true,調用contains("manager.computer")返回false。

代碼示例:

Selectors s = new Selectors();

s.add("department");

s.add("manager.manager");

s.add("manager.manager.manager");

System.out.println(s.generateSubSelectors("manager"));

System.out.println(s.contains("computer"));

System.out.println(s.contains("manager.manager"));

運行結果:

[manager.manager, manager]

false

true

接下來咱們來定義DTO生成器的接口,這個接口將可以轉換單個PO爲DTO,也能夠批量轉換多個PO爲DTO,並且這個接口還應該容許用戶指定轉換哪些屬性。

【例10.2】定義DTO生成器的接口。

代碼以下:

// DTO生成器接口

public interface IDTOGenerator

{

   

    public List generateDTOList(List list, Selectors selectors);

   

    public List generateDTOList(List list);

   

    public Object generateDTO(Object srcBean, Selectors selectors);

    public Object generateDTO(Object srcBean);

}

對於沒指定Selectors 參數的generateDTO、generateDTOList方法則不返回關聯屬性的值,只返回根一級的屬性。

大部分DTOGenerator的子類都將會直接循環調用generateDTO來完成generateDTOList方法,因此定義一個抽象基類來抽象出這個行爲。

【例10.3】DTO生成器抽象基類。

代碼以下:

// DTO生成器抽象基類

package com.cownew.PIS.framework.bizLayer;

import java.util.ArrayList;

import java.util.List;

import com.cownew.PIS.framework.common.db.Selectors;

abstract public class AbstractDTOGenerator implements IDTOGenerator

{

    public List generateDTOList(List list, Selectors selectors)

    {

        List retList = new ArrayList(list.size());

        for (int i = 0, n = list.size(); i < n; i++)

        {

            Object srcOV = list.get(i);

            retList.add(generateDTO(srcOV, selectors));

        }

        return retList;

    }

    public List generateDTOList(List list)

    {

        List retList = new ArrayList(list.size());

        for (int i = 0, n = list.size(); i < n; i++)

        {

            Object srcOV = list.get(i);

            retList.add(generateDTO(srcOV));

        }

        return retList;

    }

}

10.7.2  Hibernate的元數據

Hibernate中有一個很是豐富的元數據模型,含有全部的實體和值類型數據的元數據。

Hibernate提供了ClassMetadata接口、CollectionMetadata接口和Type層次體系來訪問元數據。能夠經過SessionFactory獲取元數據接口的實例。

ClassMetadata catMeta = sessionfactory.getClassMetadata(Cat.class);

Object[] propertyValues = catMeta.getPropertyValues(fritz);

String[] propertyNames = catMeta.getPropertyNames();

Type[] propertyTypes = catMeta.getPropertyTypes();

Map namedValues = new HashMap();

for (int i = 0; i < propertyNames.length; i++)

{

    if (!propertyTypes[i].isEntityType()

        && !propertyTypes[i].isCollectionType())

    {

        namedValues.put(propertyNames[i], propertyValues[i]);

    }

}

經過將持久化對象的類做爲參數調用SessionFactory的getClassMetadata方法就能夠獲得關於此對象的全部元數據信息的接口ClassMetadata。下面是ClassMetadata接口的主要方法說明。

l   public String getEntityName():獲取實體名稱。

l   public String getIdentifierPropertyName():獲得主鍵的名稱。

l   public String[] getPropertyNames():獲得全部屬性名稱(不包括主鍵)。

l   public Type getIdentifierType():獲得主鍵的類型。

l   public Type[] getPropertyTypes():獲得全部屬性的類型(不包括主鍵)。

l   public Type getPropertyType(String propertyName):獲得指定屬性的類型。

l   public boolean isVersioned():實體是不是版本化的。

l   public int getVersionProperty():獲得版本屬性。

l   public boolean[] getPropertyNullability():獲得全部屬性的「是否容許爲空」屬性。

l   public boolean[] getPropertyLaziness():獲得全部屬性的「是否LazyLoad」屬性。

l   public boolean hasIdentifierProperty():實體是否有主鍵字段。

l   public boolean hasSubclasses():是否有子類。

l   public boolean isInherited():是不是子類。

ClassMetadata 接口有getPropertyTypes()、getPropertyNullability()這樣平面化的訪問全部字段屬性的方法,這些方法是供 Hibernate內部實現用的,在外部使用的時候咱們經常須要深刻每一個屬性的內部,這樣藉助於getPropertyNames()、 getPropertyType(String propertyName)兩個方法就能夠知足要求了。

ClassMetadata entityMetaInfo = sessionFactory

    .getClassMetadata(destClass);

String[] propertyNames = entityMetaInfo.getPropertyNames();

for (int i = 0, n = propertyNames.length; i < n; i++)

{

    String propertyName = propertyNames[i];

    Type propType = entityMetaInfo.getPropertyType(propertyName);

    …

}

getPropertyType(String propertyName)方法返回的類型爲Type,這個類型包含了字段的元數據信息。Type接口只是一個父接口,它有不少子接口和實現類,圖10.2是它的主要的子接口和實現類的結構圖。

圖10.2  Type接口層次圖

Hibernate中的集合類型的基類是 CollectionType,其子類分別對應着數組類型(ArrayType)、Bag類型(BagType)、List類型(ListType)、 Map類型(MapType)、Set類型(SetType)。而「多對一」和「一對一」類型分別爲ManyToOneType和 OneToOneType,它們的基類爲EntityType。BigDecimal、Boolean、String、Date等類型則屬於 NullableType的直接或者間接子類。

Type接口的主要方法列舉以下。

l   public boolean isAssociationType():此類型是否能夠轉型爲AssociationType,並不表示此屬性是關聯屬性。

l   public boolean isCollectionType():是不是集合類型。

l   public boolean isComponentType():是不是Component類型,若是是的話必須能轉型爲AbstractComponentType類型。

l   public boolean isEntityType():是不是實體類型。

l   public boolean isAnyType():是不是Any類型。

l   public int[] sqlTypes(Mapping mapping):取得實體各個字段的SQL類型,返回值的類型遵照java.sql.Types中的定義。

l   public Class getReturnedClass():返回值類型。

l   public String getName():返回類型名稱。

【例10.4】Hibernate元數據接口調用。

示例代碼以下:

package com.cownew.Char15;

import org.hibernate.SessionFactory;

import org.hibernate.metadata.ClassMetadata;

import org.hibernate.type.Type;

import com.cownew.PIS.base.permission.common.UserInfo;

import com.cownew.PIS.framework.bizLayer.hibernate.HibernateConfig;

public class HibernateMetaTest

{

    public static void main(String[] args)

    {

        SessionFactory sessionFactory =
                    HibernateConfig.getSessionFactory();

        ClassMetadata entityMetaInfo = sessionFactory

                .getClassMetadata(UserInfo.class);

        String[] propertyNames = entityMetaInfo.getPropertyNames();

        for (int i = 0, n = propertyNames.length; i < n; i++)

        {

            String propertyName = propertyNames[i];

            Type propType = entityMetaInfo.getPropertyType(propertyName);

            System.out.println(propertyName + "字段類型爲"

                    + propType.getReturnedClass().getName());

        }

        if (entityMetaInfo.hasIdentifierProperty())

        {

            String idPropName = entityMetaInfo.getIdentifierPropertyName();

            Type idPropType = entityMetaInfo.getIdentifierType();

            System.out.println("主鍵字段爲:" + idPropName + "類型爲"

                + idPropType.getReturnedClass().getName());

        } else

        {

            System.out.println("此實體無主鍵");

        }

    }

}

運行結果:

number字段類型爲java.lang.String

password字段類型爲java.lang.String

person字段類型爲com.cownew.PIS.basedata.common.PersonInfo

permissions字段類型爲java.util.Set

isSuperAdmin字段類型爲java.lang.Boolean

isFreezed字段類型爲java.lang.Boolean

主鍵字段爲:id類型爲java.lang.String

10.7.3  HibernateDTO產生器

【例10.5】HibernateDTO產生器示例。

代碼以下:

// HibernateDTO產生器

package com.cownew.PIS.framework.bizLayer.hibernate;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.HashSet;

import java.util.Iterator;

import java.util.List;

import java.util.Map;

import java.util.Set;

import org.hibernate.SessionFactory;

import org.hibernate.metadata.ClassMetadata;

import org.hibernate.proxy.HibernateProxyHelper;

import org.hibernate.type.ArrayType;

import org.hibernate.type.CollectionType;

import org.hibernate.type.EntityType;

import org.hibernate.type.ListType;

import org.hibernate.type.MapType;

import org.hibernate.type.SetType;

import org.hibernate.type.Type;

import com.cownew.PIS.framework.bizLayer.AbstractDTOGenerator;

import com.cownew.PIS.framework.common.db.Selectors;

import com.cownew.ctk.common.PropertyUtils;

import com.cownew.ctk.common.ExceptionUtils;

public class HibernateDTOGenerator extends AbstractDTOGenerator

{

    private SessionFactory sessionFactory;

    public HibernateDTOGenerator(SessionFactory sessionFactory)

    {

        super();

        this.sessionFactory = sessionFactory;

    }

    public Object generateDTO(Object srcBean, Selectors selectors)

    {

        try

        {

            return copyValueObject(srcBean, selectors);

        } catch (InstantiationException e)

        {

            throw ExceptionUtils.toRuntimeException(e);

        } catch (IllegalAccessException e)

        {

            throw ExceptionUtils.toRuntimeException(e);

        }

    }

    private Object copyValueObject(Object srcVO, Selectors selectors)

            throws InstantiationException, IllegalAccessException

    {

        // 取得被代理以前的類型

        Class destClass = HibernateProxyHelper

                .getClassWithoutInitializingProxy(srcVO);

        Object newBean = destClass.newInstance();

        ClassMetadata entityMetaInfo = sessionFactory

                .getClassMetadata(destClass);

        String[] propertyNames = entityMetaInfo.getPropertyNames();

        for (int i = 0, n = propertyNames.length; i < n; i++)

        {

            String propertyName = propertyNames[i];

            Type propType = entityMetaInfo.getPropertyType(propertyName);

            // 若是不是實體類型也不是集合類型,即普通類型,則直接拷貝這些屬性

            if (!(propType instanceof EntityType)

                    && !(propType instanceof CollectionType))

            {

                Object value = PropertyUtils.getProperty(srcVO,
                        propertyName);

                PropertyUtils.setProperty(newBean, propertyName, value);

            } else if (selectors != null)

            {

                Selectors subSelector = selectors

                        .generateSubSelectors(propertyName);

                // 若是是集合屬性,而且用戶在selectors中聲明要求此屬性,

                // 則複製這些屬性

                if (propType instanceof CollectionType

                        && selectors.contains(propertyName))

                {

                    Object collValue = generateCollectionValue(srcVO,

                            (CollectionType) propType, propertyName,

                            subSelector);

                    PropertyUtils.setProperty(newBean, propertyName,
                                collValue);

                }

                // 若是是實體屬性,而且用戶在selectors中聲明要求此屬性

                // 則複製這些屬性

                else if (selectors.contains(propertyName))

                {

                    Object oldVO = PropertyUtils.getProperty(srcVO,

                            propertyName);

                    if (oldVO != null)

                    {

                        Object obj = copyValueObject(oldVO, subSelector);

                        PropertyUtils.setProperty(newBean, propertyName, obj);

                    }

                }

            }

        }

        // 因爲主鍵字段沒有在getPropertyNames中,因此要複製主鍵

        String idPropName = entityMetaInfo.getIdentifierPropertyName();

        Object value = PropertyUtils.getProperty(srcVO, idPropName);

        PropertyUtils.setProperty(newBean, idPropName, value);

        return newBean;

    }

   

    private Object generateCollectionValue(Object srcVO, CollectionType
                type,String propertyName, Selectors subSelector)

            throws InstantiationException, IllegalAccessException

    {

        if (type instanceof SetType)

        {

            Set valueSet = new HashSet();

            Set oldSet = (Set) PropertyUtils.getProperty(srcVO,
                                                        propertyName);

            Iterator oldIt = oldSet.iterator();

            while (oldIt.hasNext())

            {

                Object oldValue = oldIt.next();

                if (oldValue != null)

                {

                    Object obj = copyValueObject(oldValue, subSelector);

                    valueSet.add(obj);

                }

            }

            return valueSet;

        } else if (type instanceof ArrayType)

        {

            Object[] oldArray = (Object[]) PropertyUtils.getProperty(srcVO,

                    propertyName);

            Object[] valueArray = new Object[oldArray.length];

            for (int i = 0, n = oldArray.length; i < n; i++)

            {

                Object oldValue = oldArray[i];

                if (oldValue != null)

                {

                    valueArray[i] = copyValueObject(oldValue, subSelector);

                }

            }

            return valueArray;

        } else if (type instanceof ListType)

        {

            List oldList = (List) PropertyUtils

                    .getProperty(srcVO, propertyName);

            List valueList = new ArrayList(oldList.size());

            for (int i = 0, n = oldList.size(); i < n; i++)

            {

                Object oldValue = oldList.get(i);

                if (oldValue != null)

                {

                    valueList.add(copyValueObject(oldValue, subSelector));

                }

            }

            return valueList;

        } else if (type instanceof MapType)

        {

            Map oldMap = (Map) PropertyUtils.getProperty(srcVO,
                                                            propertyName);

            Map valueMap = new HashMap(oldMap.size());

            Set keySet = oldMap.keySet();

            Iterator keyIt = keySet.iterator();

            while (keyIt.hasNext())

            {

                Object key = keyIt.next();

                Object oldValue = oldMap.get(key);

                if (oldValue != null)

                {

                    valueMap.put(key, copyValueObject(oldValue,
                                                            subSelector));

                }

            }

            return valueMap;

        } else if (type instanceof SetType)

        {

            Set oldSet = (Set) PropertyUtils.getProperty(srcVO,
                                                            propertyName);

            Set valueSet = new HashSet(oldSet.size());

            Iterator it = oldSet.iterator();

            while (it.hasNext())

            {

                Object oldValue = it.next();

                if (oldValue != null)

                {

                    Object copyValue = copyValueObject(oldValue,
                                                        subSelector);

                    valueSet.add(copyValue);

                }

            }

            return valueSet;

        }

        throw new IllegalArgumentException("unsupport Type:"

                + type.getClass().getName());

    }

    public Object generateDTO(Object srcBean)

    {

        try

        {

            return copyValueObject(srcBean);

        } catch (InstantiationException e)

        {

            throw ExceptionUtils.toRuntimeException(e);

        } catch (IllegalAccessException e)

        {

            throw ExceptionUtils.toRuntimeException(e);

        }

    }

   

    private Object copyValueObject(Object srcVO) throws
                InstantiationException,IllegalAccessException

    {

        Class destClass = HibernateProxyHelper

                .getClassWithoutInitializingProxy(srcVO);

        Object newBean = destClass.newInstance();

        ClassMetadata entityMetaInfo = sessionFactory

                .getClassMetadata(destClass);

        String[] propNames = entityMetaInfo.getPropertyNames();

        for (int i = 0, n = propNames.length; i < n; i++)

        {

            String propName = propNames[i];

            Type fType = entityMetaInfo.getPropertyType(propName);

            if (!(fType instanceof EntityType)

                    && !(fType instanceof CollectionType))

            {

                Object value = PropertyUtils.getProperty(srcVO, propName);

                PropertyUtils.setProperty(newBean, propName, value);

            }

        }

        String idPropName = entityMetaInfo.getIdentifierPropertyName();

        Object value = PropertyUtils.getProperty(srcVO, idPropName);

        PropertyUtils.setProperty(newBean, idPropName, value);

        return newBean;

    }

}

類的核心方法就是copyValueObject、generateCollectionValue,它們分別負責生成關聯實體和集合屬性。

在copyValueObject中首先調用Hibernate的工具類HibernateProxyHelper提供的getClassWithoutInitializingProxy方法來獲得被LazyLoad代理以前的類名,好比:

getClassWithoutInitializingProxy(session.load(PersonInfo.class, id))返回PersonInfo.class。

getClassWithoutInitializingProxy(new PersonInfo())也將返回PersonInfo.class。

這是去掉LazyLoad這個包袱的最重要的一步。

接着用反射的方法獲得getClassWithoutInitializingProxy方法返回的類型的實例。

最後使用Hibernate的元數據API逐個判斷實體的各個字段的屬性,若是字段是普通字段(既不是實體類型也不是集合類型)則直接使用PropertyUtils來拷貝字段屬性;若是字段是集合屬性,而且用戶在selectors中聲明要求此屬性,則調用generateCollectionValue方法來生成新的集合屬性;若是是實體屬性,而且用戶在selectors中聲明要求此屬性,則遞歸調用copyValueObject方法來取得這個實體屬性。須要注意的是在字段是非普通屬性的時候,須要調用Selectors的generateSubSelectors方法來更換Selectors的相對根,這就達到了從左到右的逐級深刻地取得關聯屬性值的目的。

generateCollectionValue方法用來根據源bean生成新的集合屬性。由於Hibernate中集合字段的類型都是基於接口的,因此此處咱們使用這些接口的任意實現類就能夠。

調用代碼示例:

SessionFactory sessionFactory = HibernateConfig.getSessionFactory();

Session session = sessionFactory.openSession();

UserInfo userInfo = (UserInfo) session.load(UserInfo.class,

"1111111111111111111-88888888");

HibernateDTOGenerator dtoGenerator = new HibernateDTOGenerator(

sessionFactory);

Selectors selectors = new Selectors();

selectors.add("person");

UserInfo newUser1 = (UserInfo) dtoGenerator.generateDTO(userInfo);

System.out.println(newUser1.getNumber());

UserInfo newUser2 = (UserInfo) dtoGenerator.generateDTO(userInfo,

selectors);

System.out.println(newUser2.getPerson().getName());

10.7.4  通用DTO生成器

HibernateDTOGenerator比較完美地解決了DTO的產生的問題,因爲使用Hibernate自己的元數據機制,因此這個DTOGenerator能夠脫離案例系統使用。並非全部的 ORM工具都提供了像Hibernate同樣的元數據機制,因此對於這樣的ORM就必須使用案例系統的元數據機制。代碼的實現和 HibernateDTOGenerator很是相似,不過因爲根據PO獲得DTO的方式在各個ORM之間的差別很是大,好比在Hibernate中PO 的類名就是DTO的類名,而在EJB的實體Bean中PO和DTO的類名沒有直接關係,這就須要使用某種命名約定來決定DTO的類名(好比DTO類名爲實體Bean類名加「DTO」)。CommonDTOGenerator只能是一個抽象類,把根據PO獲得DTO等不能肯定的邏輯留到具體的子類中實現。

【例10.6】通用DTO生成器示例。

通用DTO生成器的代碼以下:

// 通用DTO生成器

abstract public class CommonDTOGenerator extends AbstractDTOGenerator

{

    public Object generateDTO(Object srcBean, Selectors selectors)

    {

        try

        {

            return copyValueObject((IValueObject) srcBean, selectors);

        } catch (InstantiationException e)

        {

            throw ExceptionUtils.toRuntimeException(e);

        } catch (IllegalAccessException e)

        {

            throw ExceptionUtils.toRuntimeException(e);

        }

    }

    public Object generateDTO(Object srcBean)

    {

        try

        {

            return copyValueObject((IValueObject) srcBean);

        } catch (InstantiationException e)

        {

            throw ExceptionUtils.toRuntimeException(e);

        } catch (IllegalAccessException e)

        {

            throw ExceptionUtils.toRuntimeException(e);

        }

    }

   

    protected abstract Class getRealClass(Object bean);

    private IValueObject copyValueObject(IValueObject srcVO, Selectors
            selectors)throws InstantiationException, IllegalAccessException

    {

        Class destClass = getRealClass(srcVO);

        IValueObject newBean = (IValueObject) destClass.newInstance();

        EntityModelInfo eInfo = ServerMetaDataLoaderFactory.getLoader()

                .loadEntityByVOClass(destClass);

        List fields = eInfo.getFields();

        for (int i = 0, n = fields.size(); i < n; i++)

        {

            EntityFieldModelInfo fInfo = (EntityFieldModelInfo) fields.get(i);

            if (!fInfo.isLinkProperty())

            {

                Object value = PropertyUtils.getProperty(srcVO,
                        fInfo.getName());

                PropertyUtils.setProperty(newBean, fInfo.getName(), value);

            } else if (selectors != null)

            {

                Selectors subSelector = selectors.generateSubSelectors

                                        (fInfo.getName());

                if (fInfo.getLinkType().equals(LinkTypeEnum.ONETOMANY)

                        && selectors.contains(fInfo.getName()))

                {

                    //TODO:支持其餘集合屬性,好比List

                    Set valueSet = new HashSet();

                    Set oldSet = (Set) PropertyUtils.getProperty(srcVO, fInfo

                            .getName());

                    Iterator oldIt = oldSet.iterator();

                    while (oldIt.hasNext())

                    {

                        IValueObject oldValue = (IValueObject) oldIt.next();

                        if (oldValue != null)

                        {

                            IValueObject obj = copyValueObject(oldValue,

                                    subSelector);

                            valueSet.add(obj);

                        }

                    }

                    PropertyUtils.setProperty(newBean, fInfo.getName(),
                                valueSet);

                } else if (selectors.contains(fInfo.getName()))

                {

                    Object oldVO = PropertyUtils

                            .getProperty(srcVO, fInfo.getName());

                    if (oldVO != null)

                    {

                        IValueObject obj = copyValueObject(

                                (IValueObject) oldVO, subSelector);

                        PropertyUtils.setProperty(newBean, fInfo.getName(),
                                    obj);

                    }

                }

            }

        }

        return newBean;

    }

    private IValueObject copyValueObject(IValueObject srcVO)

            throws InstantiationException, IllegalAccessException

    {

        Class destClass = getRealClass(srcVO);

        IValueObject newBean = (IValueObject) destClass.newInstance();

        EntityModelInfo eInfo = ServerMetaDataLoaderFactory.getLoader()

                .loadEntityByVOClass(destClass);

        List fields = eInfo.getFields();

        for (int i = 0, n = fields.size(); i < n; i++)

        {

            EntityFieldModelInfo fInfo = (EntityFieldModelInfo)
                                                        fields.get(i);

            if (!fInfo.isLinkProperty())

            {

                Object value = PropertyUtils.getProperty(srcVO,
                                                        fInfo.getName());

                PropertyUtils.setProperty(newBean, fInfo.getName(), value);

            }

        }

        return newBean;

    }

}

在CommonDTOGenerator中將getRealClass方法設爲抽象方法等待子類實現。在copyValueObject方法中目前支持的集合類型僅支持Set類型的屬性,之後能夠增長對List、Map、數組等類型的支持。

若是規定DTO類名爲實體Bean類名加「DTO」,就能夠編寫下面的EJBDTOGenerator:

public class EJBDTOGenerator extends CommonDTOGenerator

{

    protected Class getRealClass(Object bean)

    {

        String entityBeanClassName = bean.getClass().getName();

        String dtoClassName = entityBeanClassName + "DTO";

        try

        {

            return Class.forName(dtoClassName);

        } catch (ClassNotFoundException e)

        {

            throw ExceptionUtils.toRuntimeException(e);

        }

    }

}

採用案例系統的元數據來實現DTOGenerator就能夠保證不依賴於具體ORM,這就是元數據的好處,壞處就是這個EJBDTOGenerator是沒法將案例系統的元數據機制剝離的。

相關文章
相關標籤/搜索