昨天总结了一个关于dom解析xml的东西
今天作为昨天内容的一个补充吧,昨天那个点下面连接
java jdk 自带 DOM 解析xml 笔记
xml 转java object
今天补充一个jaxb的xml解析方法。
这个jaxb也是jdk自带的解析xml方法,他的方式与dom不同。dom是直接将xml解析成dom 树进行循环迭代进行读取,添加操作。而jaxb是 直接将xml转成相对应的数据对象。先写一个简单的例子
一个简单的数据对象类User
@XmlRootElement(name = "User")
public class User {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User [name=" + name + "]";
}
}
还有一个对应的xml
<User><name>sss</name></User>
以及一个JAXB处理xml的方法 ,详细说明看样例注释吧
// 解析的xml声明
static String xml="<User><name>sss</name></User>";
public static void main(String a[])throws Exception {
// 单独声明没负值是为了更好的说明
Class<?> clazz; //关键的参数。就是你转换完成后的对象。
String xmlStr; // 需要转换的xml
// 实际的需要进行复制
clazz=User.class; //当前需要转的的是user对象
xmlStr=xml; // 需要转的xml内容为对应的userxml
//jaxb 的声明 需要传入需要的class
JAXBContext context = JAXBContext.newInstance(clazz);
//就叫他转换器吧
Unmarshaller unmarshaller = context.createUnmarshaller();
//转reader
StringReader sr = new StringReader(xmlStr);
//进行转换
Object xmlObject = unmarshaller.unmarshal(sr);
// 这里若需要具体使用某类型需要进行强转一下
User u=(User)xmlObject;
// 打印输出
System.out.println(u.toString());
}
这个需要补充说明一点。对于转java 对象。那么再数据对象类需要声明
@XmlRootElement(name = "User")
要么转换会失败无法找到对应的元素,
JAVA Object 转 xml
看代码吧。
public static void main(String a[]) throws Exception {
// java 对象 转 xml
// 创建一个数据对象
User u = new User();
u.setName("张三");
// 根据数据对象构造一个jaxb对象
JAXBContext context = JAXBContext.newInstance(User.class);
// 创建一个转换器
Marshaller mar = context.createMarshaller();
// 设置一下
mar.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
// 创建一个字符串的输出流
StringWriter sw = new StringWriter();
// 开整
mar.marshal(u, streamWriter);
// 输出
streamWriter.flush();
// 关闭
streamWriter.close();
// 打印
System.out.println(op.toString("UTF-8"));
}
输出结果
<?xml version="1.0" ?><User><name>张三</name></User>
完成。
稍微复杂点的xml解析
xml稍作改动
<User id='root'>
<name id='1'>sss</name>
<cdata><![CDATA[ddddddd]]></cdata>
</User>
解析方法不变 主要是针对数据对象,进行了适当的改造。
User 数据对象
@ToString
@Getter
@XmlRootElement(name = "User")
public class User {
String id;
Name n;
String cdata;
@XmlElement(name = "name")
public void setN(Name n) {
this.n = n;
}
@XmlAttribute(name = "id")
public void setId(String id) {
this.id = id;
}
@XmlElement(name = "cdata")
public void setCdata(String cdata) {
this.cdata = cdata;
}
}
新增的Name 数据对象
@Getter
@ToString
public class Name {
String id;
String value;
@XmlAttribute(name = "id")
public void setId(String id) {
this.id = id;
}
@XmlValue
public void setValue(String value) {
this.value = value;
}
}
可以看到这些对象主要是在set方法中添加了相对应的注解。
具体都在javax.xml.bind.annotation 包下。
看看运行后输出结果
User(id=root, n=Name(id=1, value=sss), cdata=ddddddd)
可以看到完美的解析出来了。
用对象转xml也一样。将刚才的数据对象进行赋值操作然后。再进行转换
赋值代码
User u = new User();
u.setId("uid");
u.setCdata("data value");
Name n=new Name();
n.setId("nid");
n.setValue("张三");
u.setN(n);
执行后返回结果
<?xml version="1.0" ?>
<User id="uid">
<cdata>data value</cdata>
<name id="nid">张三</name>
</User>
可以看到除了CDATA 类型数据没有转换完成,其余就转换完成了。
CDATA 的操作
下来说说关于cdata的类型数据转xml 的操作
对于Cdata 的操作很简单,通过再反射过程中增加一个过程就行了。就是做一个Adapter
看代码
// 一个专门用于CData的处理,继承自XmlAdapter
public class CDataAdapter extends XmlAdapter<String, String> {
// 转对象时候拿到值需要怎么做
@Override
public String unmarshal(String v) throws Exception {
return v;
}
// 输出值的时候需要怎么做。这里只用加上CDATA 标签就行
@Override
public String marshal(String v) throws Exception {
return new StringBuilder("<![CDATA[").append(v).append("]]>").toString();
}
}
然后对数据对象加上对应注释
@XmlJavaTypeAdapter(CDataAdapter.class)
而user 对象中
@XmlElement(name = "cdata")
@XmlJavaTypeAdapter(CDataAdapter.class)
public void setCdata(String cdata) {
this.cdata = cdata;
}
运行后获取结果
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<User id="uid">
<cdata><![CDATA[data value]]></cdata>
<name id="nid">张三</name>
</User>
这里出现了对< > 尖括号的转义。那么需要对转义进行过滤。需要修改Marshaller 部分的代码。
对于Marshaller 部分需要进行
// 不进行转义字符的处理
marshaller.setProperty(CharacterEscapeHandler.class.getName(), new CharacterEscapeHandler() {
public void escape(char[] ch, int start, int length, boolean isAttVal, Writer writer) throws IOException {
writer.write(ch, start, length);
}
});
这里需要设置一个处理对象。CharacterEscapeHandler
com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler
如此后进行输出则完成需求
输出结果
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<User id="uid">
<cdata><![CDATA[data value]]></cdata>
<name id="nid">张三</name>
</User>
其他补充与说明。
对于注解部分与对应get set方法说明。
JAXBContext 创建的 Unmarshaller 与Marshaller 都是基于java 的反射实现的。
所以对于VO 类来说get 与set方法尤为重要。至于后面是什么没所谓。剩下的是依赖于注解来进行填充与调用。
举例:
// 对于这个id 的解析。默认我们规范的都是setId
@XmlAttribute(name = "id")
public void setId(String id)
// 修改一下为setIddddddddddddd 这样。xml也是照样可以解析的
@XmlAttribute(name = "id")
public void setIddddddddddddd(String id)
// 但是这样 开始不为set 则无法解析。会报错
@XmlAttribute(name = "id")
public void etId(String id)
对于cdata 转义时候出现的问题
做cdata 的时。期初我的代码对于输出流默认是这样的:
摘录部分代码:
ByteArrayOutputStream op = new ByteArrayOutputStream();
XMLOutputFactory xof = XMLOutputFactory.newInstance();
XMLStreamWriter streamWriter = xof.createXMLStreamWriter(op);
Marshaller mar = context.createMarshaller();
mar.marshal(u, streamWriter);
需要注意的是 通过factory 创建的这个XmlStreamWriter 是对 Marshaller 的setProperty 有影响的。具体为什么没有看。
这么创建writer 对于后续的代码
mar.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
等等设置是没有任何作用的。所以导致了对cdata 中的尖括号转义等是没有任何办法的。直接换成 StringWriter 后解决了这个问题。