Blog信息 |
blog名称: 日志总数:1304 评论数量:2242 留言数量:5 访问次数:7601312 建立时间:2006年5月29日 |

| |
[Apache(jakarta)]Commons-betwixt和Xstream比较浅析 软件技术
lhwork 发表于 2006/9/27 10:11:14 |
Commons-betwixt和Xstream比较浅析1. 原理及流程简介1.1 commons-betwixt² 基本原理Commons Betwixt这个组件提供了一个XML自省(introspection)机制用来把Java Bean映射成XML信息(.xml文件或符合xml格式的字符串,下同)或者把XML信息映射成Java Bean。在解析XML信息时采用SAX方式。² 核心类,方法及步骤简介n xml信息映射成java beanorg.apache.commons.betwixt.io.BeanReader 要把xml信息映射成 JavaBean,这个类是最核心的类。首先要构造此类的一个对象。然后配置该对象属性,(包括给XML信息是否匹配Attribute或Id等等),下来最重要的一步是给这个对象注册一个BeanClass,即要转化的Java Bean的类名,函数原型为:void registerBeanClass(String path, Class beanClass),其中path为XML信息中要转化的element名。接下来就是最后一步,调用核心方法Object parse(String),这个方法不是BeanReader本身的。而是它继承的org.apache.commons.digester.Digester里面的方法,这里不在叙述。n java bean映射成xml信息org.apache.commons.betwixt.io.BeanWriter 要把Java Bean 映射成XML信息,此为核心类。同样,首先要构造此类的一个对象,然后配置该对象(包括是否给生成的XML信息设置格式化,Attribute或Id等等),然后调用核心方法write(String name,Object bean)name是即将生成的XML信息的element名,bean为待转化的对象。此方法是BeanWriter自身的方法。² 需要的支持环境:commons-digester.jar,commons-beanutils.jar,commons-codec.jar,commons-lang.jar,dom4j-1.3.jar1.2 Xstream² 基本原理Xstream这个组件也提供了一个XML自省(introspection)机制用来把Java Bean映射成XML信息或者把XML信息映射成Java Bean。不同的是在解析xml信息时采用的是DOM方式² 核心类,方法及步骤简介Xstream在把xml信息和java bean相互转换的过程中用到同一个应用类。com.thoughtworks.xstream.XStream同commons-betwixt组件一样,首先需要建立一个XStream的对象,但是它少了配置一步(其实是它自己在程序里已经配置好了),下来也和commons-betwixt组件一样,最重要的一步,给这个对象注册一个BeanClass,即要转化的Java Bean的类名,函数原型为:public void alias(String name, Class type),其中name为xml信息中的element名称,下来就可以调用核心方法fromXML() 和toXML(Object)把xml信息转化成java bean或把对象转化成xml信息了。² 需要的支持环境:xpp3-1.1.3.4d_b4_min.jar,xom-1.0b3.jar,stax-api-1.0.jar,jdom-1.0.jar,dom4j-1.3.jar,commons-lang-2.0.jar,jmock-2004-03-19.jar,joda-time-1.0.jar,stax-1.1.1-dev.jar,xml-writer-0.2.jar2. 举例说明2.1 用到的Java Bean信息为了更能体现出效果,就用com.guangyu.pmv4.model.User类来做测试之用。² User对象User user = newUser();//parmentDepartment dep =new Department();dep.setId(Long.valueOf(1));dep.setName("杭州广域");dep.setSerialTitle("pSerialTitle");dep.setType(newInteger(234));user.setDepartment(dep); // user.setId(Long.valueOf(2)); Set grants = newHashSet();grants.add("删除");grants.add("添加");grants.add("更新");grants.add("查看");user.setAccessGrants(grants);user.setFirstViceCategory(newInteger(2));user.setUserName("姚亮");user.setUserId("luna");user.setType(newInteger(2));user.setStatus(newInteger(1));user.setPassword("11111111");user.setIpAddress("10.13.45.178");user.setId(Long.valueOf(1));user.setFirstViceCategory(newInteger(2));² Xml信息两种方法在解析时用到的xml信息是各自在第一步由User对象转化成String时得到的,两者得到的xml信息有很大的差别,影响着各自解析的性能,在后面的分析说明里将详细说明2.2 commons-betwixt解析举例² Java Bean转换成xml Stringn 代码BeanWriter writer= new BeanWriter();writer.getXMLIntrospector().getConfiguration() .setAttributesForPrimitives(false);writer.getBindingConfiguration().setMapIDs(false);writer.enablePrettyPrint();writer.write(user);n 结果 <User> <accessGrants> <accessGrant>查看</accessGrant> <accessGrant>添加</accessGrant> <accessGrant>更新</accessGrant> <accessGrant>删除</accessGrant> </accessGrants> <department> <id>1</id> <name>杭州广域</name> <serialTitle>pSerialTitle</serialTitle> <subDepartments/> <type>234</type> <users/> </department> <endDate/> <firstViceCategory>2</firstViceCategory> <id>1</id> <ipAddress>10.13.45.178</ipAddress> <mainCategory/> <password>11111111</password> <secondViceCategory/> <startDate/> <status>1</status> <type>2</type> <userId>luna</userId> <userName>姚亮</userName> </User>² xml转化成Java Beann 代码BeanReaderreader = new BeanReader();reader.getXMLIntrospector().getConfiguration() .setAttributesForPrimitives(false);reader.getBindingConfiguration().setMapIDs(false);reader.registerBeanClass(“User”,User.class);User u = (User)reader.parse(new StringReader(value));n 运行时间。(不包含log)100次共耗时1328毫秒;平均运行一次耗时不到14毫秒2.3 Xstream解析举例² Java Bean转化成xml 信息n 代码如下XStream s = newXStream();s.alias("user",User.class);s.alias("department",Department.class);System.out.println(s.toXML(user));n 结果如下<user> <id>1</id> <status>1</status> <userId>luna</userId> <userName>姚亮</userName> <password>11111111</password> <type>2</type> <ipAddress>10.13.45.178</ipAddress> <department> <id>1</id> <name>杭州广域</name> <type>234</type> <serialTitle>pSerialTitle</serialTitle> </department> <accessGrants> <string>查看</string> <string>添加</string> <string>更新</string> <string>删除</string> </accessGrants> <firstViceCategory>2</firstViceCategory></user>² xml信息转化成Java Beann 代码如下XStream s = newXStream();s.alias("user",User.class);User u = (User) s.fromXML(value);n 运行时间。(不包含log)运行100次共耗时790毫秒,平均解析一次耗时7.9,不到8毫秒3 运行环境硬件环境intel2.66GHZ+1,048,048mRAM软件环境见1.1和1.2中具体的环境支持说明4 两者对比浅析4.1 commons-betwixt浅析² 优点n 方便,快速。构造相应的对象,配置好以后就可以开始xml和java Bean的转化,尤其解析xml信息时有多种参数的方法供调用,可以直接对文件进行解析。而在解析xml信息到java bean时,小于14毫秒的速度足以使之成为解析xml产品中的佼佼者。(写到这里自己先寒一个,感觉在推销产品^_^)n 强壮。一个好的组件能被广泛流传不仅仅是因为好用,还要足够强壮,能处理一些异常的情况,比如待解析的xml信息如果包含javabean以外的标签,例如在上面的例子中,给xml信息加入<good>notgood</good>之类的java bean里没有的属性,betwixt在解析的时候会跳过这些标签,同时正确的解析其他的属性。而且如果javabean自身的属性为空,比如<userName/>,则betwixt解析的时候会将相应的属性设置为null。在上面的例子中还可以把<user>…</user>全部包括在另外的标签之下,比如<relatedObject><user>…</user></relatedObject>,但是一定要在配置的时候指定解析的路径,比如:reader.registerBeanClass(“relatedObject/user”, User.class);² 缺点n 笨。其实betwixt是很笨的,它在解析的时候都要去找Java Bean里面的getter,setter和adder函数来匹配相应的标签值。而且Java Bean还要执行了Serializable,所以解析之前,必须先在类里定义好诸如此类的方法。另外一点在一个数据结构里面只能出现同一种对象。例如一个List对象,它里面只能存放同一种对象。n 对特殊字符的支持不够,如果在标签里面出现”<”或”>”,bwtwixt一定会抛出异常的,例如<userName>hehe>heeh</userName>.还有其他的一些特殊字符,具体哪些支持那些不支持现在还不是很清楚。n 解析xml的时候有好几种调用方法 ,其中有一个的参数是String,public Objectparse(String),经常会出现unknownSource的异常,很怪异。到现在还不知所以然,所以建议调用它的另外一个方法 public Object parse(Reader)4.2 Xstream浅析² 优点n 如果bwtwixt用方便快速来形容的话,那Xstream只能说是,简直太方便,简直太快速了。在xml解析的产品中,如果光看方便性和速度,它一定首屈一指,可惜它却有别的瑕疵,这是后话了,先来看看它到底有多么方便吧。Xstream在解析xml信息时,不需要配置(其实betwixt的配置也没有什么实质性的东西,事实上也是可以去掉的),不需要java bean执行Serializable,不需要getter,setter和adder函数(想不明白这些都不需要,它是怎么把属性添加进去的,但是事实上它却做到了),甚至不需要注册Class(就是alias()方法,相当于betwixt里面的registerBeanCalss()方法)就可以把xml转化成你想要的对象,例如在本文的例子中,如果把xml信息的user标签由原来的<user>…</user>改成<com.guangyu.pmv4.model.user>…</com.guangyu.pmv4.model.user>,就不需要注册类了,仅仅两步就可以完成:XStream stream =new XStream();User user = (User) Stream.fromXML(value);另外,它的速度更快,解析一次不到8毫秒。傻了吧?^_^n 对特殊字符的支持。如果在标签里面出现<username>hehe>hehe</username>的情况,它一样可以解析成功。n 在同一种数据结构里面可以存放不同的对象。例如在一个List对象里面既可以有对象A,也可以有对象B,但是一定要注册类。例如:String xml = “<list><a>…</a><b>…</b><list>”;XStream stream =new XStream();stream.alias(“list”,ArrayList.class);stream.alias(“a”,A.class);stream.alias(“b”,B.class);List list =(List) stream.fromXML(xml);这样解析后的list对象就同时有A和B对象² 缺点n 不够强壮。这也许成为了Xstream的致命弱点。Xstream在解析的时候碰到诸如<userName/>的标签,它不会简单得跳过,而是用一个空的字符串””去设置匹配相应的参数,所以往往会出错,而如果这个属性是Integer,Long之类的属性,它就一定会出错,并抛出异常。Xml信息里更不能有javabean属性以外的标签出现,它也不支持把整个xml信息包含在一个大的element里,象<relatedObject><user>…</user><relatedObject>它也是一定会抛异常的。n 一个不是缺点的缺点,但是一时的疏忽可能会造成程序运行时出错.例如,如果在要转化的javaBean里面有一个List属性的字段,解析的时候Xstream会使用默认的ArrayList,而如果你的程序里的setter方法用的是一个LinkedList结构(Xstream不会去看你的setter方法),那在使用解析后的对象时就可能出错。解决的办法是在定义这个字段的时候最好就声明它到底是哪一个结构,或者初始化,下面两种方法均可以解决这个问题。例如:private LinkedList xxx;或者 private List xxx = new LinkedList();4.3 对比浅析² 把对象转化成xml信息的时候,两种方法最大的区别就是:如果某个字段为null,betwixt会解析成<xxx/>,而Xstream根本不做处理,也就是说转化后的xml信息里面就没有这个标签。看上面的示例就可以体会到。² 在把xml解析成java bean的时候,注册类的思路不同。Betwixt注册的是一个绝对路径,而Xstream注册的是一个相对路径。例如:registerBeanClass(“relatedObject/user”,User.class);(betwixt)alias(“user”,User.calss)(Xstream)² Xstream更方便,更快速,betwixt在这方面稍逊一筹,但是也很方便,快速。² Betwixt更强壮。这是由betwixt的映射机制决定的,betwixt是根据类里面的getter,setter和adder方法去匹配相应的属性的。而这一点也决定了它的稳定性。而Xstream在这方面就差了很多。² 底层原理不同,betwixt使用的是SAX原理,Xstream使用的是DOM原理,而这个属性就决定了两个组件的不同命运,性能也会受到底层的影响。比如Xstream就不适合处理很大信息的情况。而这个就是由DOM自身的特性决定的。² 另外还要说明一点,这些天忙的事情也比较多,没有好好的研究Xstream,可能有的地方我的理解存在着问题。所以希望抱着批判的态度看待这篇报告。^_^Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=502840
luna 发表于2005-10-13 16:34:00 IP: 211.100.21.*附上User的源代码
import java.io.Serializable;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import com.guangyu.pmv4.LoadUserAccessGrants;
import org.apache.commons.lang.builder.ToStringBuilder;
public class User implements Serializable
{
/**
* 用户id.
*/
private Long id;
/**
* 用户状态.
*/
private Integer status;
/**
* 用户帐号.
*/
private String userId;
/**
* 用户名称.
*/
private String userName;
/**
* 用户密码.
*/
private String password;
/**
* 用户类型.
*/
private Integer type;
/**
* 网络地址.
*/
private String ipAddress;
/**
* 所属部门.
*/
private Department department;
/**
* 用户有效开始日期.
*/
private String startDate;
/**
* 用户有效截止日期.
*/
private String endDate;
/**
* 用户的权限集合.
*/
private Set accessGrants;
/**
* 用户的主类别.必需.部门的类型.
*/
private Integer mainCategory;
/**
* 用户的第一个辅肋类别.
*/
private Integer firstViceCategory;
/**
* 用户的第二个辅助类别.
*/
private Integer secondViceCategory;
/**
* 得到用户所在部门.
* @return department
*/
public Department getDepartment()
{
return department;
}
/**
* 设定用户所在部门.
* @param aDepartment 所属部门.
*/
public void setDepartment(final Department aDepartment)
{
this.department = aDepartment;
}
/**
* 得到截止日期.
* @return 截止日期.
*/
public String getEndDate()
{
return endDate;
}
/**
* 设定截止日期
* @param aEndDate 截止日期
*/
public void setEndDate(final String aEndDate)
{
this.endDate = aEndDate;
}
/**
* 得到用户id.
* @return id.
*/
public Long getId()
{
return id;
}
/**
* 设定用户id.
* @param aId 用户id.
*/
public void setId(final Long aId)
{
this.id = aId;
}
/**
* 得到网络地址.
* @return 网络地址.
*/
public String getIpAddress()
{
return ipAddress;
}
/**
* 设定网络地址.
* @param aIpAddress 网络地址.
*/
public void setIpAddress(final String aIpAddress)
{
this.ipAddress = aIpAddress;
}
/**
* 得到密码.
* @return 密码.
*/
public String getPassword()
{
return password;
}
/**
* 设定密码
* @param aPassword 密码
*/
public void setPassword(final String aPassword)
{
this.password = aPassword;
}
/**
* 得到开始日期.
* @return 开始日期.
*/
public String getStartDate()
{
return startDate;
}
/**
* 设定开始日期.
* @param aStartDate 开始日期.
*/
public void setStartDate(final String aStartDate)
{
this.startDate = aStartDate;
}
/**
* 得到用户状态.
* @return 用户状态.
*/
public Integer getStatus()
{
return status;
}
/**
* 设定用户状态.
* @param aStatus 用户状态.
*/
public void setStatus(final Integer aStatus)
{
this.status = aStatus;
}
/**
* 得到用户类型.
* @return 用户类型.
*/
public Integer getType()
{
return type;
}
/**
* 设定用户类型.
* @param aType 用户类型.
*/
public void setType(final Integer aType)
{
this.type = aType;
}
/**
* 得到用户帐号.
* @return 用户帐号.
*/
public String getUserId()
{
return userId;
}
/**
* 设定用户帐号.
* @param aUserId 用户账号.
*/
public void setUserId(final String aUserId)
{
this.userId = aUserId;
}
/**
* 得到用户名称.
* @return 用户用户名称.
*/
public String getUserName()
{
return userName;
}
/**
* 设定用户名称.
* @param aUserName 用户名.
*/
public void setUserName(final String aUserName)
{
this.userName = aUserName;
}
/**
* 判断用户可否被删除.
* @return 可否删除boolean.
*/
public boolean onRemove()
{
return true;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(final Object object)
{
if (!(object instanceof User))
{
return false;
}
User user = (User) object;
return this.getId().equals(user.getId());
}
/**
* @see java.lang.Object#hashCode()
*/
public int hashCode()
{
return super.hashCode();
}
/**
* 得到用户的权限集合.
* @return Set
*/
public Set getAccessGrants()
{
return this.accessGrants;
}
/**
* 设置用户权限.
* @param pAccessGrants 权限值.
*/
public void setAccessGrants(final Set pAccessGrants)
{
this.accessGrants = pAccessGrants;
}
/**
* 增加用户的权限.
* @param pString 权限.
*/
public void addAccessGrant(final String pString)
{
if (this.accessGrants == null)
{
accessGrants = new HashSet();
}
this.accessGrants.add(pString);
}
/**
* @return Returns 用户的主类别.
*/
public Integer getMainCategory()
{
return mainCategory;
}
/**
* @param pMainCategory 用户的主类别.
*/
public void setMainCategory(final Integer pMainCategory)
{
this.mainCategory = pMainCategory;
}
/**
* @return Returns 用户的第一个辅助类别.
*/
public Integer getFirstViceCategory()
{
return firstViceCategory;
}
/**
* @param pFirstViceCategory 用户的第一个辅助类别.
*/
public void setFirstViceCategory(final Integer pFirstViceCategory)
{
this.firstViceCategory = pFirstViceCategory;
}
/**
* @return Returns 用户的第二个辅助类别.
*/
public Integer getSecondViceCategory()
{
return secondViceCategory;
}
/**
* @param pSecondViceCategory 用户的第二个辅助类别.
*/
public void setSecondViceCategory(final Integer pSecondViceCategory)
{
this.secondViceCategory = pSecondViceCategory;
}
/**
* @see java.lang.Object#toString()
*/
public String toString()
{
return new ToStringBuilder(this)
.append("secondViceCategory", this.secondViceCategory)
.append("type", this.type).append("endDate", this.endDate)
.append("ipAddress", this.ipAddress).append("status",
this.status).append("mainCategory", this.mainCategory)
.append("password", this.password).append("startDate",
this.startDate).append("userId", this.userId).append(
"firstViceCategory", this.firstViceCategory).append(
"userName", this.userName).toString();
}
}
|
|
|