控制JAXB的输入输出

来源:互联网 发布:分布式数据库系统 编辑:程序博客网 时间:2024/06/05 10:02

上一节介绍了如何在解析模型的时候构建模型之间的父子链,其实使用afterUnmarshal()或beforeUnmarshal()方法或Unmarshaller.Listener都可以用来参与到模型的解析过程,也就是输入过程。关于输入过程的参与没有过多的说明,这节主要介绍输出的参与。

 

一般情况下,所有声明的jaxb的属性和元素都会事无巨细的被保存到xml的文件中,例如还是使用上例中Students的例子,可能保存的文件内容如下:

Xml代码  收藏代码
  1. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>  
  2. <ns2:students xmlns:ns2="http://www.liulutu.com/students/">  
  3.     <student sex="Female" name="Bob" birthday="2013-11-27+08:00" />  
  4. </ns2:students>  

 假设,默认的值就是Female,那就可以省略sex的设置,期望的内容就变成了:

Xml代码  收藏代码
  1. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>  
  2. <ns2:students xmlns:ns2="http://www.liulutu.com/students/">  
  3.     <student name="Bob" birthday="2013-11-27+08:00" />  
  4. </ns2:students>  

在继续之前,有一点需要说明一下就是:如果想某项内容不输入,则可以通过设置该项内容为空来实现。

所以要想让Female不输入,则需要想办法让它变成空。

 

方法一:使用public void beforeMarshal(Marshaller m)方法

可以在StudentType里定义public void beforeMarshal(Marshaller m)方法,其中方法内容为:

Java代码  收藏代码
  1. public void beforeMarshal(Marshaller m) {  
  2.     if (SexType.FEMALE == this.sex) {  
  3.         this.sex = null;  
  4.     }  
  5. }  

这样修改的一个潜在问题就是在这之后再读取sex变量,值可能就是空的,因为也需要修改getSex()方法,让它在null值的情况下返回一个默认值:

Java代码  收藏代码
  1. public SexType getSex() {  
  2.     return sex==null?SexType.FEMALE:sex;  
  3. }  

 或者引入一个中间变量,就是不直接存储sex (把它标记成,而是引入一个dummySex,这个对象外界修改不了,只在保存的时候才有用,例如: 

Java代码  收藏代码
  1. @XmlAttribute(name = "sex")  
  2. protected SexType dummySex = null;  
  3.   
  4. @XmlTransient  
  5. protected SexType sex;  

 

Java代码  收藏代码
  1. public void beforeMarshal(Marshaller m) {  
  2.     if (SexType.FEMALE != this.sex) {  
  3.         this.dummySex = this.sex;  
  4.     }  
  5. }  

 不过这样一来,就需要在解析的时候做dummySex到sex的映射(使用unmarshal()方法可以做到),和上面相比,没什么改进。

 

根据上面的说明:只有值是空的时候才不保存,那对于像int,float之类的primitive类型就有问题了,因为他们没有办法设置为null,一个可能的变通方法就是将这些primitive对象类型定义为他们的wrap类,例如Integer,Float等,然后就可以解决这个问题了。

 

方法二:使用Marshaller.Listener

同方法一类型,只是把各个模型中定义beforeMarshal()方法变成定义在监听事件里。

 

方法三:使用XmlAdapter

上面介绍的方法都有一定的缺点:要么要把内容设置为空;要么要引入某种中介变量。实际上,在保存之后,所以有内容都可以认为是一个普通的字符串,所以可以想办法把所以的内容改变成某个符串再输入就可以了,然后根据需求,决定是转成一个有意义的字符串还是一个null字符,如果是null字符串,则结果就不会被保存。

 

因为我们可以使用XmlAdapter来实际从某个字符到某个类型的转换,如上描述,最简单的就是所有的内容都转换成或null的字符串,或类型对应的字符串内容。例如从上面的SexType转成字符串的XmlAdapter的实现:

Java代码  收藏代码
  1. import javax.xml.bind.annotation.adapters.XmlAdapter;  
  2.   
  3. public class SexTypeXmlAdapter extends XmlAdapter<String, SexType> {  
  4.   
  5.     @Override  
  6.     public String marshal(SexType v) throws Exception {  
  7.         if (v == null || v == SexType.FEMALE) {  
  8.             return null;  
  9.         }  
  10.         return v.name();  
  11.     }  
  12.   
  13.     @Override  
  14.     public SexType unmarshal(String v) throws Exception {  
  15.         if (v == null) {  
  16.             return SexType.FEMALE;  
  17.         }  
  18.         return SexType.valueOf(v);  
  19.     }  
  20.   
  21. }  

 同样的,只有在不是FEMALE的时候才保存。不过这里用的是XmlAdapter来实现的,所以再也不需要beforeMarshal()方法,也不需要引入中介变量。不过我需要告诉JAXB,说在sex变量上给我应用这个转换,这个通过在sex变量上添加以下声明实现:

Java代码  收藏代码
  1. @XmlAttribute(name = "sex")  
  2. @XmlJavaTypeAdapter(SexTypeXmlAdapter.class)  
  3. protected SexType sex;  

 这样就实现我们的意图。

 

关于XmlAdapter的更多内容:

看看XmlJavaTypeAdapter的声明:

Java代码  收藏代码
  1. @Retention(RUNTIME) @Target({PACKAGE,FIELD,METHOD,TYPE,PARAMETER})  
  2. public @interface XmlJavaTypeAdapter  

可以看出这个annotation可以用在从包级到参数级的所有级别上,这样的一个好外就是:假设我的类中或者包中有很多这样SexType类型,那要一个一个添加就相当麻烦了,这个时候就可以所这个声明加在更高一级的对象上,例如加在类级别上,则所有该类中的此类对象都自动应用。

 

@XmlJavaTypeAdapters

除此之外,还有另一个annotation @XmlJavaTypeAdapters,从名字上可以看出它是XmlJavaTypeAdapter的复数形式,就是说可以包含多个@XmlJavaTypeAdapter声明:

Java代码  收藏代码
  1. @Retention(RUNTIME) @Target({PACKAGE})  
  2. public @interface XmlJavaTypeAdapters   

从声明中可以看出,这个只能用在包级别上,例如:

Java代码  收藏代码
  1. @XmlJavaTypeAdapters({@XmlJavaTypeAdapter(ItemTypeValueAdapter.class),@XmlJavaTypeAdapter(BooleanValueAdapter.class), @XmlJavaTypeAdapter(StringValueAdapter.class), @XmlJavaTypeAdapter(IntValueAdapter.class)})  
  2. package cn.com.bjfanuc.assessment.models;  
  3.   
  4. import javax.xml.bind.annotation.adapters.*;  

 不过这里有一个问题:怎么在包级别上使用这些annotation呢?这就涉及到package-info.java文件了。

 

pacakge-info.java

这个文件是一个特殊的java文件,通常用来声明一些和包相关的信息,不能含有公共或私有类声明,关于它的介绍,可以参考http://strong-life-126-com.iteye.com/blog/806246 .

上面的@XmlJavaTypeAdapters就需要定义在这样的一个文件里。其实包名和引用的依赖需要声明。

 

CDATA

如果想把某个字段保存成cdata类型,我没发现什么好方法,好像总是需要做某种特殊处理,否则< 或 >会被转义。

例如,我们想name保存成以下格式:

Xml代码  收藏代码
  1. <name><![CDATA[ bob ]]></name>  

首先修改一下name的annotation,从attribute改成element

Java代码  收藏代码
  1. @XmlElement(name="name")  
  2. protected String name;  

然后要做的就怎么让它变成一个CDATASection,方法之一就是如上面所示,用XmlAdapter来做,如下:

Java代码  收藏代码
  1. public class CDATASectionAdapter extends XmlAdapter<String, String> {  
  2.   
  3.     @Override  
  4.     public String unmarshal(String v) throws Exception {  
  5.         return v;  
  6.     }  
  7.   
  8.     @Override  
  9.     public String marshal(String v) throws Exception {  
  10.         if(v != null){  
  11.             return "<![CDATA["+v+"]]>";  
  12.         }  
  13.         return null;  
  14.     }  
  15.   
  16. }  

然后在name上使用这个adapter

Java代码  收藏代码
  1. @XmlElement(name="name")  
  2. @XmlJavaTypeAdapter(CDATASectionAdapter.class)  
  3. protected String name;  

这个时候,输入的内容大致:

Xml代码  收藏代码
  1. <name>&lt;![CDATA[Bob]]&gt;</name>  

可以看到,最前的<和最后的>都被转义了。因此我们需要告诉Marshaller,碰到cdata的时候不要转义。可以通过给Marshaller设置定义的CharacterEscapeHandler属性来实现。

首先,看看我们的自定义气CharacterEscapeHandler类:

Java代码  收藏代码
  1. class CustomCharacterEscapeHandler implements CharacterEscapeHandler {  
  2.     private int cdataMiniLength = "<![CDATA[]]>".length();  
  3.     private static String[] escapeList = new String[63];  
  4.     static {  
  5.         escapeList[(int'&'] = "&amp;";  
  6.         escapeList[(int'<'] = "&lt;";  
  7.         escapeList[(int'>'] = "&gt;";  
  8.         escapeList[(int'"'] = "&quot;";  
  9.         escapeList[(int'\t'] = "&#x9;";  
  10.         escapeList[(int'\r'] = "&#xD;";  
  11.         escapeList[(int'\n'] = "&#xA;";  
  12.     }  
  13.   
  14.   
  15.     public void escape(char[] ch, int start, int length,  
  16.             boolean isAttVal, Writer out) throws IOException {  
  17.         if (isAttVal) {  
  18.             for (char c : ch) {  
  19.                 if (escapeList.length > ((int) c)  
  20.                         && escapeList[(int) c] != null) {  
  21.                     out.write(escapeList[(int) c]);  
  22.                 } else {  
  23.                     out.write(c);  
  24.                 }  
  25.             }  
  26.         } else {  
  27.             if (length >= cdataMiniLength) {  
  28.                 String s = new String(ch).trim();  
  29.                 if (s.startsWith("<![CDATA[")  
  30.                         && s.endsWith("]]>")) {  
  31.                     out.write(ch);  
  32.                     return;  
  33.                 }  
  34.             }  
  35.             for (char c : ch) {  
  36.                 if (c == '"' || c == '\t' || c == '\n') {  
  37.                     out.write(c);  
  38.                 } else if (escapeList.length > ((int) c)  
  39.                         && escapeList[(int) c] != null) {  
  40.                     out.write(escapeList[(int) c]);  
  41.                 } else {  
  42.                     out.write(c);  
  43.                 }  
  44.             }  
  45.         }  
  46.     }  
  47.   
  48. }  
  49.    

这个类定义了自己的转义方法,并且attribute和非attribute的转义字符数还不一样。另外就是当碰到以  <![CDATA[ 开头和 ]]> 结尾的串时不做转义。

 

最后就是把这个自定义的类应用到Marshaller上去:

Java代码  收藏代码
  1. marshaller.setProperty(CharacterEscapeHandler.class.getName(),  
  2.         new CustomCharacterEscapeHandler());  

 最后,再运行输入如下:

Xml代码  收藏代码
  1. <name><![CDATA[Bob]]></name>  

 对于Unmarshall那端,不需要做什么修改,可以测试一下:

Java代码  收藏代码
  1. JAXBContext context = JAXBContext.newInstance(Students.class);  
  2. Unmarshaller unmarshaller = context.createUnmarshaller();  
  3. Students model = (Students) unmarshaller.unmarshal(new File("a.xml"));  
  4. List<StudentType> student = model.getStudent();  
  5. for(StudentType st: student){  
  6.     System.out.println(st.getName());  
  7. }  

 

0 0
原创粉丝点击