SECTION 01 JavaBean 与 Betwixt 简介
其实, JavaBean 在 Java 初期就已经有的东西, 大家可以下载他的 Specification 1.0.1 查阅, 在标准的 JDK 中, 都会存在着 java.beans.* 这个 package, 而 JavaBean 到底是做些什么呢, 最重要的是, 只要是我们可以重复利用到的组件, 都可以把他做成一颗颗的 JavaBean, 而这次和 betwixt 有关的, 就是 BeanInfo Interface, 可以参阅他的教学文件, 此外, 好的 JavaBean 该如何制作, 可以参考 How to be a Good Bean .
而 Betwixt 就是提供了 XML introspection 的 机制去对应 JavaBeans 到 XML, 他实现出 XMLIntrospector 及 XMLBeanIfno 相似于 jdk 中标准的 Introspector 及 BeanInfo (都在 java.beans.* 之下), 提供了将 beans 转换成 XML 的方法, 可以自动产生 digester 的规则, 也可以根据个人的打字习惯设定不同的 BeanInfo 机制等等. 而 Maven, Scarab, commons-sql 都有采用到 betwixt 这些功能. 相似的项目有 JAXB, Castor, XMLBeans, JiBX 等等, 不过 Betwixt 是其中最简单去操控的, 而且可以与 EJB RemoteInterface 结合. 他算是以 Beans-centric 而 Castor 比较算是 Schema-centric.
目前版本为 1.0 alpha
binary 下载处 http://www.apache.org/dist/jakarta/commons/betwixt/binaries/commons-betwixt-1.0-alpha-1.zip
source 下载处 http://www.apache.org/dist/jakarta/commons/betwixt/source/commons-betwixt-1.0-alpha-1-src.zip
SECTION 02 Beans 和 XML 的对应关系
假设我们有下面这个 CustomerBean, 我们可以用两种 xml 表示方式来代表他存在的数值. public class CustomerBean {
public String getName();
public Order[] getOrders();
public String[] getEmailAddresses();
}
第一种, 我可以把 name 的值设为 CustomerBean 的属性. <CustomerBean name='James'>
<order id='1'>...</order>
<order id='2'>...</order>
<emailAddress>jstrachan@apache.org</emailAddress>
</CustomerBean>
第二种, 我也可以把 name 拉出来成为 CustomerBean 的一个节点. <customer>
<name>James</name>
<orders>
<order id='1'>...</order>
<order id='2'>...</order>
</orders>
<email-addresses>
<email-address>jstrachan@apache.org</email-address>
</email-addresses>
</customer>
SECTION 03 简单的 BeanWriter 范例程序
首先, PersonBean.java 中, 我们就只是单纯的建立一个标准的 JavaBean, 具有一个空的 contructor ( 为了 reflection ), getters 及 setters, 外加一个 toString(), 为了简化范例程序, 我们另外建立了一个可以带入参数的 contructor. public class PersonBean {
private String name;
private int age;
/** Need to allow bean to be created via reflection */
public PersonBean() {}
public PersonBean(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "PersonBean[name='" + name + "',age='" + age + "']";
}
}
接着, 我们使用 WriteExampleApp 来解释 betwixt 的操作模式, 我们先打印出 xml 的标头, 接着, 设置 beanWriter 的相关属性, 再来建立一个 John Smith 的 PersonBean, 我们只需要使用 beanWriter.write 就可以把 PersonBean 的 XML 数据放到 outputWriter 之中, 而 outputWriter.toString() 就会显示 PersonBean 的 XML 数据. import java.io.StringWriter;
import org.apache.commons.betwixt.io.BeanWriter;
public class WriteExampleApp {
/**
* Create an example bean and then convert it to xml.
*/
public static final void main(String [] args) throws Exception {
// Start by preparing the writer
// We'll write to a string
StringWriter outputWriter = new StringWriter();
// Betwixt just writes out the bean as a fragment
// So if we want well-formed xml, we need to add the prolog
outputWriter.write("<?xml version='1.0' ?>");
// Create a BeanWriter which writes to our prepared stream
BeanWriter beanWriter = new BeanWriter(outputWriter);
// Configure betwixt
// For more details see java docs or later in the main documentation
beanWriter.getXMLIntrospector().setAttributesForPrimitives(false);
beanWriter.setWriteIDs(false);
beanWriter.enablePrettyPrint();
// Write example bean as base element 'person'
beanWriter.write("person", new PersonBean("John Smith", 21));
// Write to System.out
// (We could have used the empty constructor for BeanWriter
// but this way is more instructive)
System.out.println(outputWriter.toString());
}
}
执行的结果是 <?xml version='1.0' ?>
<person>
<age>21</age>
<name>John Smith</name>
</person>
如果我们将 setAttributesForPrimitives 设为 true,执行的结果是. ( 只要是 primitive 的数值都视为 xml 的属性 ) <?xml version='1.0' ?>
<person age="21" name="John Smith"/>
SECTION 04 简单的 BeanReader 范例程序
我们之前使用 commons-digester, 可以将 xml 数据读入 javabean 之中, 而 betwixt 的 BeanReader 就是靠著 digester 将 xml 数据读入, 不过他多了一个 registerBeanClass 来注册 Bean , 先建立一个 person.xml 文件放到执行目录中. <?xml version='1.0' ?>
<person>
<age>30</age>
<name>Gary Lee</name>
</person>
接着, 我们先设定好 beanReader 的相关属性, 采用 parse 将 XML 数据读取到 beanReader 之中, 再来就是放入 person 这个 bean 之内, 因此, person 将拥有 person.xml 所设定的数值. 最后将使用 PersonBean.toString() 将数据显示出来. import java.io.*;
import org.apache.commons.betwixt.io.BeanReader;
public class ReadExampleApp {
public static final void main(String args[]) throws Exception{
// Now convert this to a bean using betwixt
// Create BeanReader
BeanReader beanReader = new BeanReader();
// Configure the reader
// If you're round-tripping, make sure that the configurations are compatible!
beanReader.getXMLIntrospector().setAttributesForPrimitives(false);
beanReader.setMatchIDs(false);
// Register beans so that betwixt knows what the xml is to be converted to
// Since the element mapped to a PersonBean isn't called the same,
// need to register the path as well
beanReader.registerBeanClass("person", PersonBean.class);
// Read Data From person.xml and parse it
PersonBean person
= (PersonBean)beanReader.parse(new File("person.xml"));
// send bean to system out
System.out.println(person);
}
}
执行的结果是 PersonBean[name='Gary Lee',age='30']
SECTION 05 strategy 的一些技巧
如果我们直接采用 BeanWriter 印出 TallTreeBean 的 XML 文件, writer.write(new TallTreeBean(15.1f)); public class TallTreeBean {
private float heightOfTree;
public TallTreeBean(float height) {
setHeightOfTree(height);
}
public float getHeightOfTree() {
return heightOfTree;
}
public void setHeightOfTree(float heightOfTree) {
this.heightOfTree = heightOfTree;
}
}
将会产生下面的 xml 数据, 但是我们的 XML 属性习惯不会采用复和字, heightOfTree 我们希望用 height-of-tree 来显示, 此外, TallTreeBean 开头我们通常也是采用小写字元 tallTreeBean 来表示. <TallTreeBean heightOfTree="15.1"/>
因此我们程序改为 import org.apache.commons.betwixt.io.BeanWriter;
import org.apache.commons.betwixt.strategy.DecapitalizeNameMapper;
import org.apache.commons.betwixt.strategy.HyphenatedNameMapper;
public class NameMapperExampleApp {
public static final void main(String args[]) throws Exception{
// create write and set basic properties
BeanWriter writer = new BeanWriter();
writer.getXMLIntrospector().setAttributesForPrimitives(true);
writer.enablePrettyPrint();
writer.setWriteIDs(false);
// set a custom name mapper for attributes
writer.getXMLIntrospector().setAttributeNameMapper(new HyphenatedNameMapper());
// set a custom name mapper for elements
writer.getXMLIntrospector().setElementNameMapper(new DecapitalizeNameMapper());
// write out the bean
writer.write(new TallTreeBean(15.1f));
System.out.println("");
}
}
HyphenatedNameMapper() 就是将复合字用"-"来切割, DecapitalizeNameMapper() 就是将首字改为小写, 此外 org.apache.commons.betwixt.strategy 下面还有
BadCharacterReplacingNMapper | NameMapper implementation that processes a name by replacing or stripping illegal characters before passing result down the chain. | CapitalizeNameMapper | A beanmapper which converts a type to start with an uppercase. | ClassNormalizer | Class normalization strategy. | ConvertUtilsObjectStringConverter | String <-> object conversion strategy that delegates to ConvertUtils. | DecapitalizeNameMapper | A name mapper which converts types to a decapitalized String. | DefaultNameMapper | A default implementation of the name mapper. | DefaultObjectStringConverter | Default string <-> object conversion strategy. | DefaultPluralStemmer | A default implementation of the plural name stemmer which tests for some common english plural/singular patterns and then uses a simple starts-with algorithm | HyphenatedNameMapper | A name mapper which converts types to a hypenated String. | ListedClassNormalizer | ClassNormalizer that uses a list of substitutions. | ObjectStringConverter | Strategy class for string <-> object conversions. | 大家可以自己尝试一下各种状况,
例如你的 bean 是属于 public class SomeBean {
public <CollectionType> getFoo*();
public void addFoo(<SingularType> foo);
}
CollectionType 可能是 array, a Collection, Enumeration, Iterator, Map 等等, 而 SingularType 是属于 Foo 的属性, 就是采用 DefaultPluralStemmer 来处理.
SECTION 06 结论
Betwixt 目前对于 DynaBean 的处理还有一些问题, 不过许多高级的处理, 例如整合 EJB 都有简单的方法处理, 当读取一个复杂的 bean 时, 可以自行定义 *.betwixt 文件, 让程序去正确地读取. 我不在此一一讲述, 有兴趣的可以到 commons-betwixt 的网站中, 查阅 advanced 的技巧. |