OXmlEd1.11使用指南
沈东良
2007-8-22
简介
诞生
OXmlEd项目是一个“对象--XML映射”(Object-Xml Mapping)的类库。它的目的是帮助开发者方便、快速的从XML文件构建出Java对象,从Java对象生成出相应的XML文件。
OXmlEd是一个开源的项目,诞生于 2007 年 8 月 9 日 ,由沈东良(英文名Edward Shen,网名良少)创建,项目地址:https://sourceforge.net/projects/oxmled/
在我的Blog上也会有相应的介绍文章。http://blog.csdn.net/shendl/
概况
OXmlEd项目最新的版本是1.11版本。一共经历了3个版本。1.0版本,实现了编程方式的“对象—XML映射”。1.1版本实现了标注(Annotation)方式的“对象—XML映射”。最新的1.11版本是对1.1版本的完善的BUG修复。
目前,还没有经受全面的测试,可能会有一些问题,欢迎用户到项目网站或者我的Blog、Email上报告BUG。
通过OXmlEd项目的开发,我深深体会到做开源软件的不易!平时工作就很忙,周末又有私事,因此留给开发开源软件的时间就更加少了。
但是,既然我创建了OXmlEd项目,就必须尽快把它做完,不能半途而废。因此,开发OXmlEd项目是在死亡进度的恶劣条件下开发的。测试驱动开发,一些设计、重构等等,能省则省,只为了在及其有限的时间内开发出尽可能多的功能。因此,项目的质量有所下降。等功能开发完毕之后,如果有时间,我将好好重构代码。
使用OXmlEd进行“对象—XML映射”
使用标注把你的Java对象映射为XML文件
使用OXmlEd库,你只需要使用2个标注(XMLNode,XMLAttribute)就可以方便地把任意java类表示成XML文件,也可以从生成的XML文件重新得到Java对象。
Test源文件夹下的net.sf.oxmled.mapping.sample包下的Test类和Person类是2个测试类。
参考它们,你可以为你自己的java类添加标注,实现与XML文件的映射。
Test示例
/**
* 1,根节点,应该指定为root。
* 2,如果编码是默认的utf-8,那么属性值可以是中文,但属性的名字,节点的名字等如果是中文,就会是乱码。
* 因此,设为gbk。
* 只有根节点需要在类定义上标注XMLNode,如果是作为子节点,则不需要在类上进行标注。 可以在属性上添加标注!
*
* 注意:在基类上声明属性是无用的! 以为无法反射得到那些私有字段!
*/
@XMLNode(root=true,encoding="gbk")
public class Test {
/**
* 属性。
* 指定了从xml文件的属性值转到Java类的字段的值的方法。 通过这个方法,可以根据XML文件的相应属性的String值,得到java对象中
* 该属性的实际的int值。
*/
@XMLAttribute(castMethod=IConvertCastUtil.castInt)
private int a=1;
/**
* 上面的castMethod=IConvertCastUtil.castInt 并没有实际指定使用这个方法。而是用了一个专门的工具类的方法。
* 查找的顺序是,那个工具类的方法,如果没有,就找本地的方法。
* 如果上面的Annotation改为这样:
* @XMLAttribute(castMethod="castInt")
* 就会使用这个方法进行转换。
* @param value
* @return
*/
private int castInt(String value){
return Integer.parseInt(value);
}
/**
* 指定这个字段是需要映射为XML文件的数据项
*/
@XMLAttribute()
private String b="BBB";
@XMLAttribute(castMethod="castBoolean")
private boolean bool=false;
/**
*
* @param value
* @return
*/
private boolean castBoolean(String value){
return Boolean.parseBoolean(value);
}
@XMLAttribute(castMethod="castDouble")
private double dd=245.32d;
/**
*
* @param value
* @return
*/
private double castDouble(String value){
return Double.parseDouble(value);
}
/**
* 这个属性,保存在xml文件中时,使用“我们”作为属性的名字。
*/
@XMLAttribute(name="我们",castMethod="castInt")
private int aa=1;
/**
* 这个属性,保存在xml文件中时,使用"www"作为属性的名字。
*/
@XMLAttribute(name="www")
private String bb="BBB";
@XMLAttribute(value="true",castMethod="castBoolean")
private boolean boolbbb=false;
/**
* 这个属性,保存在xml文件中时,使用"dddda"作为属性的名字。使用固定的"111.32d"作为属性的值
*/
@XMLAttribute(name="dddda",value="111.32d",castMethod="castDouble")
private double ddd=245.32d;
@XMLAttribute
private String str="String";
/**
* 子节点 可以是一个单独的类,也可以是一个数组,一个Collection接口的实现类!
*
*/
@XMLNode()
private TestB testB=new TestB();
@XMLNode()
private TestB[] child=new TestB[]{new TestB(),new TestB(),null,new TestB(),null,new TestB()};
/**
* 子节点
*/
@XMLNode()
private AttributeList childList=new AttributeList();
/**
*
*/
public Test() {
/*
*
*/
this.testB.setSsss("I love U!");
this.childList.add("sssssssss");
this.childList.add(new TestB());
}
/**
* @param args
* @throws Exception
* @throws Exception
*/
public static void main(String[] args) throws Exception {
/*
*
*/
//IXmlUtil util=new XmlUtil();
//util.loadXmlFile(ClassLoaderUtil.getResource("net.sf.oxmled.mapping.sample.Test"));
Test test=new Test();
System.out.println(test.a);
//这是执行Object-XML Mapping的助手类
IOXmlMapping oXmlMapping=new OXmlMapping();
try {
//把java对象生成为一个xml文件。 这里会放在程序的根路径下。名字是该对象的类名。
//你也可以指定生成的位置和文件名。
oXmlMapping.save(test);
/**
* load方法,根据xml文件,生成Java对象!
*/
Test test2 =(Test) oXmlMapping.load("net.sf.oxmled.mapping.sample.Test");
System.out.println(test2.a);
System.out.println(test2.aa);
} catch (IllegalArgumentException e) {
/*
*
*/
e.printStackTrace();
} catch (MalformedURLException e) {
/*
*
*/
e.printStackTrace();
} catch (URISyntaxException e) {
/*
*
*/
e.printStackTrace();
} catch (IllegalAccessException e) {
/*
*
*/
e.printStackTrace();
} catch (IOException e) {
/*
*
*/
e.printStackTrace();
} catch (Exception e) {
/* TODO 自动生成 catch 块
*
*/
e.printStackTrace();
}
}
}
TestB类的源代码:
public class TestB {
@XMLAttribute()
private String ssss="as";
public String getSsss() {
return ssss;
}
public void setSsss(String ssss) {
this.ssss = ssss;
}
public TestB() {
/*
*
*/
}
}
Person示例
@XMLNode(root=true)
public class Person extends AbstractPerson implements java.io.Serializable {
// Fields
@XMLAttribute(castMethod=IConvertCastUtil.castWrappedLong)
private Long id;
@XMLAttribute
private String number;
@XMLAttribute
private String name;
private Byte type;
/**
* 因为这个子节点是集合,而其声明的类型是一个接口。所以,需要我们提供 具体的 集合类。java.util.HashSet
* 注意: 你必须提供有无参构造器的类,否则将无法通过反射创建对象!
*/
@XMLNode(concreteClass="java.util.HashSet")
private Set licenseTags = new HashSet(0);
……
使用方法
注意点:
1,所有标注为XMLNode和XMLAttribute的对象,或者集合中的对象,其类型都必须是有无参构造器的Java类。否则,将无法使用IOXmlMapping接口的load方法从XML文件中恢复Java对象。
2,XMLAttribute 可以标注在一个表示根节点或者子节点的Java类的属性上,不论其访问类型是什么都行。但不能放在基类上。
基类上标注的XMLAttribute是不能访问的。(下一个版本将修复这个问题,所有层次、可访问性的字段都可以加上XMLAttribute标注)
3,作为XML文件的根节点的java对象。该类需要使用@XMLNode在Class上进行声明。
如果不是作为XML文件的根节点,则无需在该类的Class上使用@XMLNode。
一个字段作为子节点,那么就在该字段上标上@XMLNode。
2个标注:
XMLNode
/**
* XML类型标注,标注在业务对象上,表示:
*1,name---节点的名字
*2,root---是否根节点
*3,encoding---编码
* 可继承,运行时,目标是类型,文档可见
*
* 用于2处:
* 1,根节点,标注在类上。
* 2,子节点,标注在字段上。
*
* 如果标注于基类的字段上,则无法反射!
*
*/
@Documented
@Retention(value=RetentionPolicy.RUNTIME)
@Inherited
@Target({ElementType.TYPE,ElementType.FIELD})
public @interface XMLNode {
/**
* 编码
* @return
*/
String encoding() default "utf-8";
/**
* 是否根节点 默认不是根节点
* @return
*/
boolean root() default false;
/**
* 节点的名字
* 如果为"",使用类名作为节点的名字
* 或者使用字段名作为节点名字---如果是子节点
* @return
*/
String name() default "";
/**
* 节点的内容
* 默认是""字符串。 表示没有
* "toString" 表示使用 该对象的toString()方法的返回值作为 内容。
*
* @return
但是,我们无法正确的从 节点的 text中生成正确的数据! 因为toString()产生的数据,如何逆向构建对象?没有办法!
String text() default "";
*/
/**
* 具体的实现类,一般只有 类型 是 数组或者集合的子节点 才需要设置它。
* 因为,从xml文件生成Java对象时,需要我们自己创建一个空的数组或者集合。
* 而类中可能使用了基类或者接口来引述它们!
* @return
*/
String concreteClass() default "";
}
说明
XMLAttribute
/**
* 用于标注字段为属性
* 如果标注于基类的字段上,则无法反射!
*
* 如果字段是iterator或者数组,则生成多个xml属性
*/
@Documented
@Retention(value=RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Inherited
public @interface XMLAttribute {
/**
* 属性的名字,默认就取是变量的名字
* @return
*/
String name() default "";
/**
* 默认就取 变量的值的toString()方法的值。
* @return
*/
String value() default "";
/**
* 转换 xml文件中的属性的字符串为需要的类型的方法。
* 转换方法的签名如下:
* IConvertCastUtil
* Type castAAA(String value);
* @return
*/
String castMethod() default "";
/**
* 目前未启用
* 这个属性用于指出该字段如何转为xml文件中的String。
* 默认就是该字段的toString方法生成的数据。
* 某些情况下,可能你需要提供这样的方法。
* @return 根据Java对象生成的属性的值
*/
String toStringMethod() default "";
//ConvertCastMethod defaultCast() default ConvertCastMethod.none;
}
说明
castMethod中,如果指定方法。如果在ConvertCastUtil类中已经有了,可以通过IConvertCastUtil接口的常量指定。
如果没有现存的方法,可以在java类中自定义转换的方法。castMethod=”方法名字”。系统将会自动调用这个转换方法。
OXmlEd工作原理
上面,你已经看到了OXmlEd库的使用标注实现“对象—XML映射”的方法。是非常简单的。不需要你进行复杂的配置,也不需要你提供XML文件的Schema等复杂的工作。
下面谈谈OXmlEd库的工作原理:
1,OXmlEd库建立在XML操作类库之上。
OXmlEd显然需要操作XML文件。“不重复发明轮子”。因此,我们目前在底层使用了Dom4j这个类库来具体操作XML文件。
OXmlEd贯彻了使用接口“解耦”的思想,完全可以使用其它XML操作类库来替代Dom4j,而不会对OXmlEd库的用户不会造成任何影响。实际上,我曾经用jdom替代过Dom4j。
2,OXmlEd库在Dom4J等类库上提供了一个助手类,可以帮助你简化一些XML操作的工作,可以看作是对Dom4J等的增强。它们存放在IXmlUtil接口和XmlUtil类中。
3,OXmlEd库有一个自己的核心的对象INode对象。这个对象可以代表所有XML文件。它是OXmlEd库的核心。
INode接口既不依赖于底层的XML操作库,也不依赖于用户需要实现的“对象—XML映射”业务类。
它的作用是:所有java类和XML文件之间的桥梁。
一个Java类对象只要转换成对等的INode对象,就可以通过OXmlEd库生成XML文件。
一个INode对象只要能够转换成为一个Java类的对象,就可以通过OXmlEd库从一个XML文件直接生成一个Java类的对象。
上图是编程方式实现“对象—XML映射”的OXmlEd库结构图
上图是标注方式实现“对象—XML映射”的OXmlEd库结构图
编程方式实现“对象—XML映射”
其实,OXmlEd库,最开始的目的,是使用编程方式实现“对象—XML映射”。
只是在OXmlEd这个开源项目发布1.0版之后,我看了一下其他的“对象—XML映射”的实现方案,发现有一些使用配置数据来实现“对象—XML映射”。
因此,我才开发了OXmlEd库的1.1版本。在OXmlEd库的核心:INode和INodeService层上又开发了一个层次:标注和IOXmlMapping层。
实现了使用标注在Java对象和XML文件之间互相映射的功能。
对于一些常用的“对象—XML映射”需求,使用标注确实要简单、迅速很多。
但是,你也可能遇到一些标注方式无法实现“对象—XML映射”的特殊情况。用户的需求常常都会出你意表,稀奇古怪!
这时,你就可以在INode和INodeService这个层次上使用OXmlEd库,通过简单的编程,实现“对象—XML映射”。
你需要在自己的java类中提供一个方法,根据INode对象创建该java类的实例。
需要在自定义的INode实现类中提供一个方法,根据你的Java类的对象,创建INode对象的一个实例。
有了这2个方法,你就可以实现任意java类和INode对象之间的映射,借助OXmlEd的功能,也就能够实现任意java类和XML文件之间的映射。
另外,我推荐你使用ChangeListener事件监听器,动态实现业务对象和INode对象之间的同步。
我提供了2个INode接口的实现类,帮助你使用ChangeListener事件监听器。我认为ChangeListener事件监听器是最合适的“观察者模式”的实现!
EventListenerNode类可以被实现ChangeListener接口的对象所监听。
ChangeListenerNode类实现了ChangeListener接口,可以监听实现了IEventListenerRegistry接口的业务对象。
你可以让INode和你的Java类互相监听数据的变化,从而实现INode和Java类之间的时刻同步,也就是实现Java类和XML文件之间的时刻同步。
使用XML格式作为配置文件
OXmlEd项目还提供了使用XML格式作为配置文件的助手类。
以前,我们一般使用属性文件作为配置文件。OXmlEd提供了一套助手类,可以让你非常方便的使用XML文件来保存你的配置信息。
1,IConfig接口,这是管理XML文件的java助手类的接口。
2,OXmlEdConfig类,这是IConfig接口的一个实现类,可以使用它来管理XML配置文件。
这是使用了OXmlEd库的INode和INodeService接口的方法实现的。
3,XmlUtilConfig类,它也是IConfig接口的一个实现类。它的功能和OXmlEdConfig类相同,只是实现方法不同。
它使用了OXmlEd库的IXmlUtil接口和Dom4j的Document类实现的。也就是说,它实际上是在OXmlEd提供的助手类的帮助下用Dom4j实现的。
之所以同时提供了2个实现类,也是希望这2个类能够作为例子,帮助用户使用OXmlEd库。
XML配置文件的格式
IConfig能够读取的XML配置文件的格式大致是这个样子的。
我对Scheml没什么研究,也没时间写严格的xml格式。
/**
* 方便的读取xml格式的配置文件。
* xml文件的类型应该是类似于这样的:
* <?xml version="1.0" encoding="gbk"?>
<!-- 配置资料 -->
<configs>
<maps>
<!--
单个值
<map>
<key>RMIServerIp</key>
<value>127.0.0.1</value>
</map>
多个值
<map>
<key>RMIServerIp</key>
<values>127.0.0.1</values>
<values>127.0.0.2</values>
</map>
<map>
<key>RMIServerIp</key>
<value>127.0.0.1</value>
<values>127.0.0.1</values>
<values>127.0.0.2</values>
</map>
-->
</maps>
</configs>
下面做一下讲解:
<map>
<key></key>
<value></value>
</map>
用来保存一个名值对。
IConfig. get(String key);方法可以得到String值。
<map>
<key></key>
<values></values>
<values></values>
</map>
可以用来保存这样的名值对:一个名字,对应多个值。
IConfig. gets(String key)方法可以得到List<String>这样的值。
<map>
<key>RMIServerIp</key>
<value>127.0.0.1</value>
<values>127.0.0.2</values>
<values>127.0.0.3</values>
</map>
这样的一个配置数据,可以通过IConfig. get(“RMIServerIp”)得到值:127.0.0.1
IConfig. gets(“RMIServerIp”)得到一个List,包含127.0.0.2和127.0.0.3。
Iconfig接口的put(String key, String value)方法可以新增或修改XML配置文件的内容。修改
<map>
<key></key>
<value></value>
</map>
Iconfig接口的put(String key, List<String> values)方法可以新增或修改XML配置文件的内容。修改
<map>
<key></key>
<values></values>
<values></values>
</map>
未来版本会添加的功能
OXmlEd项目未来版本会增加一些功能,目前摆上日程的有:
1,对节点的text内容的支持。不止在属性中保存数据,也会在节点的内容中保存数据。
2,会重构代码。目前,所有转换代码写在固定的类ConvertCastUtil中。未来会变为可配置的。不管什么类都可以。
getInstance()方法返回单例。操作项目路径下的config/config.xml文件。