层间数据传输的过程就是服务的执行者将数据返回给服务的调用者的过程。在非分布式系统中因为有相似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
在分布式系统中,客户端和服务器端交互有两种情形:第一个是客户端从服务器端读取数据;第二个是客户端将自己的数据传递给服务器端。数据库
当有客户端要向服务器端传输大量数据的时候,能够经过一个包含要传输的全部数据的方法调用来完成。这在小数据量的时候缺点并不明显,可是若是要传递包含有大量信息的数据的时候,这将变得难以忍受。下面的方法是任何人看了都会惧怕的:编程
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、数据传送哈希表、数据传送行集。
域模型是指从业务模型中抽取出来的对象模型,好比商品、仓库。在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可能作了不少更新或者很深层次的更新,要探查这些更新而后更新域对象是很麻烦的事情。
域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增长了字段,那么客户端的代码也必须从新编译,不然会产生类版本不一致的问题。
使用定制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之后的版本中再也不存在此问题。
当开发报表或者开发大数据量的客户端的时候,直接用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实现)把全部的与数据更新相关的行集操做经过异常等方式屏蔽。
上面几节比较了常见的层间数据传输模式,这些模式都有各自的优缺点,必须根据实际状况选择合适的模式,绝对不能生搬硬套、人云亦云。
考虑到系统架构的合理性,不少人都是强调避免将域对象直接传递到客户端的,由于这样服务端的域模型就暴露给了客户端,形成客户端与服务器端的高度耦合。当域模型修改的时候,就要形成客户端代码的修改或者从新编写。建议从新创建一个定制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传递给客户端就成为一个必须解决的问题。
将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是没法将案例系统的元数据机制剥离的。