DTO理論與實踐

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

10.1  什麼是DTO

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

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

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

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

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

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

ISomeInterface intf = RemoteService.getSomeInterface();

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

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();

    }

    /**

     * 產生以property爲根的新的Selectors

     */

    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;

    }

    /**

     * property屬性是否被定義在Seletors中了

     */

    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

{

    /**

     * 爲多個PO產生DTO

     * @param list DTO列表

     * @param selectors 哪些複合屬性需要轉換

     */

    public List generateDTOList(List list, Selectors selectors);

    /**

     * @see List generateDTOList(List list, Selectors selectors)

     * @param list DTO列表

     */

    public List generateDTOList(List list);

    /**

     * 爲單個PO產生DTO

     * @param srcBean

     * @param selectors 哪些複合屬性需要轉換

     */

    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;

    }

    /**

     * 生成srcVO的副本,關聯屬性由subSelector指定

     */

    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);

        }

    }

    /**

     * 得到srcVO的副本

     */

    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);

        }

    }

    /**

     * 得到bean的真實類,也就是剝離了lazyload等AOP方面以後的類,

     * 比如在hibernate中就是:

     * return HibernateProxyHelper

     *          .getClassWithoutInitializingProxy(bean)

     */

    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是無法將案例系統的元數據機制剝離的。

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