上一节介绍了如何在解析模型的时候构建模型之间的父子链,其实使用afterUnmarshal()或beforeUnmarshal()方法或Unmarshaller.Listener都可以用来参与到模型的解析过程,也就是输入过程。关于输入过程的参与没有过多的说明,这节主要介绍输出的参与。
一般情况下,所有声明的jaxb的属性和元素都会事无巨细的被保存到xml的文件中,例如还是使用上例中Students的例子,可能保存的文件内容如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <ns2:students xmlns:ns2="http://www.liulutu.com/students/"> <student sex="Female" name="Bob" birthday="2013-11-27+08:00" /> </ns2:students>
假设,默认的值就是Female,那就可以省略sex的设置,期望的内容就变成了:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <ns2:students xmlns:ns2="http://www.liulutu.com/students/"> <student name="Bob" birthday="2013-11-27+08:00" /> </ns2:students>
在继续之前,有一点需要说明一下就是:如果想某项内容不输入,则可以通过设置该项内容为空来实现。所以要想让Female不输入,则需要想办法让它变成空。
方法一:使用public void beforeMarshal(Marshaller m)方法
可以在StudentType里定义public void beforeMarshal(Marshaller m)方法,其中方法内容为:
public void beforeMarshal(Marshaller m) { if (SexType.FEMALE == this.sex) { this.sex = null; } }
这样修改的一个潜在问题就是在这之后再读取sex变量,值可能就是空的,因为也需要修改getSex()方法,让它在null值的情况下返回一个默认值:
public SexType getSex() { return sex==null?SexType.FEMALE:sex; }
或者引入一个中间变量,就是不直接存储sex (把它标记成,而是引入一个dummySex,这个对象外界修改不了,只在保存的时候才有用,例如:
@XmlAttribute(name = "sex") protected SexType dummySex = null; @XmlTransient protected SexType sex;
public void beforeMarshal(Marshaller m) { if (SexType.FEMALE != this.sex) { this.dummySex = this.sex; } }
不过这样一来,就需要在解析的时候做dummySex到sex的映射(使用unmarshal()方法可以做到),和上面相比,没什么改进。
根据上面的说明:只有值是空的时候才不保存,那对于像int,float之类的primitive类型就有问题了,因为他们没有办法设置为null,一个可能的变通方法就是将这些primitive对象类型定义为他们的wrap类,例如Integer,Float等,然后就可以解决这个问题了。
方法二:使用Marshaller.Listener
同方法一类型,只是把各个模型中定义beforeMarshal()方法变成定义在监听事件里。
方法三:使用XmlAdapter
上面介绍的方法都有一定的缺点:要么要把内容设置为空;要么要引入某种中介变量。实际上,在保存之后,所有的内容都可以认为是一个普通的字符串,所以可以想办法把所有的内容转变成某个符串再输入就可以了,然后根据需求,决定是转成一个有意义的字符串还是一个null字符,如果是null字符串,则结果就不会被保存。
我们可以使用XmlAdapter来实际从某个字符到某个类型的转换,如上描述,最简单的就是所有的内容都转换成或null的字符串,或类型对应的字符串内容。例如从上面的SexType转成字符串的XmlAdapter的实现:
import javax.xml.bind.annotation.adapters.XmlAdapter; public class SexTypeXmlAdapter extends XmlAdapter<String, SexType> { @Override public String marshal(SexType v) throws Exception { if (v == null || v == SexType.FEMALE) { return null; } return v.name(); } @Override public SexType unmarshal(String v) throws Exception { if (v == null) { return SexType.FEMALE; } return SexType.valueOf(v); } }
同样的,只有在不是FEMALE的时候才保存。不过这里用的是XmlAdapter来实现的,所以再也不需要beforeMarshal()方法,也不需要引入中介变量。不过我需要告诉JAXB,说在sex变量上给我应用这个转换,这个通过在sex变量上添加以下声明实现:
@XmlAttribute(name = "sex") @XmlJavaTypeAdapter(SexTypeXmlAdapter.class) protected SexType sex;
这样就实现我们的意图。
关于XmlAdapter的更多内容:
看看XmlJavaTypeAdapter的声明:
@Retention(RUNTIME) @Target({PACKAGE,FIELD,METHOD,TYPE,PARAMETER}) public @interface XmlJavaTypeAdapter
可以看出这个annotation可以用在从包级到参数级的所有级别上,这样的一个好外就是:假设我的类中或者包中有很多这样SexType类型,那要一个一个添加就相当麻烦了,这个时候就可以所这个声明加在更高一级的对象上,例如加在类级别上,则所有该类中的此类对象都自动应用。
@XmlJavaTypeAdapters
除此之外,还有另一个annotation XmlJavaTypeAdapters,从名字上可以看出它是XmlJavaTypeAdapter的复数形式,就是说可以包含多个XmlJavaTypeAdapter声明:
@Retention(RUNTIME) @Target({PACKAGE}) public @interface XmlJavaTypeAdapters
从声明中可以看出,这个只能用在包级别上,例如:
@XmlJavaTypeAdapters({@XmlJavaTypeAdapter(ItemTypeValueAdapter.class),@XmlJavaTypeAdapter(BooleanValueAdapter.class), @XmlJavaTypeAdapter(StringValueAdapter.class), @XmlJavaTypeAdapter(IntValueAdapter.class)}) package cn.com.bjfanuc.assessment.models; 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保存成以下格式:
<name><![CDATA[ bob ]]></name>
首先修改一下name的annotation,从attribute改成element
@XmlElement(name="name") protected String name;
然后要做的就怎么让它变成一个CDATASection,方法之一就是如上面所示,用XmlAdapter来做,如下:
public class CDATASectionAdapter extends XmlAdapter<String, String> { @Override public String unmarshal(String v) throws Exception { return v; } @Override public String marshal(String v) throws Exception { if(v != null){ return "<![CDATA["+v+"]]>"; } return null; } }
然后在name上使用这个adapter
@XmlElement(name="name") @XmlJavaTypeAdapter(CDATASectionAdapter.class) protected String name;
这个时候,输入的内容大致:
<name><![CDATA[Bob]]></name>
可以看到,最前的<和最后的>都被转义了。因此我们需要告诉Marshaller,碰到cdata的时候不要转义。可以通过给Marshaller设置定义的CharacterEscapeHandler属性来实现。
首先,看看我们的自定义气CharacterEscapeHandler类:
class CustomCharacterEscapeHandler implements CharacterEscapeHandler { private int cdataMiniLength = "<![CDATA[]]>".length(); private static String[] escapeList = new String[63]; static { escapeList[(int) '&'] = "&"; escapeList[(int) '<'] = "<"; escapeList[(int) '>'] = ">"; escapeList[(int) '"'] = """; escapeList[(int) '\t'] = "	"; escapeList[(int) '\r'] = "
"; escapeList[(int) '\n'] = "
"; } public void escape(char[] ch, int start, int length, boolean isAttVal, Writer out) throws IOException { if (isAttVal) { for (char c : ch) { if (escapeList.length > ((int) c) && escapeList[(int) c] != null) { out.write(escapeList[(int) c]); } else { out.write(c); } } } else { if (length >= cdataMiniLength) { String s = new String(ch).trim(); if (s.startsWith("<![CDATA[") && s.endsWith("]]>")) { out.write(ch); return; } } for (char c : ch) { if (c == '"' || c == '\t' || c == '\n') { out.write(c); } else if (escapeList.length > ((int) c) && escapeList[(int) c] != null) { out.write(escapeList[(int) c]); } else { out.write(c); } } } } }
这个类定义了自己的转义方法,并且attribute和非attribute的转义字符数还不一样。另外就是当碰到以 <![CDATA[ 开头和 ]]> 结尾的串时不做转义。
最后就是把这个自定义的类应用到Marshaller上去:
marshaller.setProperty(CharacterEscapeHandler.class.getName(), new CustomCharacterEscapeHandler());
最后,再运行输入如下:
<name><![CDATA[Bob]]></name>
对于Unmarshall那端,不需要做什么修改,可以测试一下:
JAXBContext context = JAXBContext.newInstance(Students.class); Unmarshaller unmarshaller = context.createUnmarshaller(); Students model = (Students) unmarshaller.unmarshal(new File("a.xml")); List<StudentType> student = model.getStudent(); for(StudentType st: student){ System.out.println(st.getName()); }
相关推荐
JAXB的使用JAXB的使用JAXB的使用JAXB的使用
无需安装,解压后即可。jaxb-api.jar,jaxb-xjc.jar,jaxb-impl.jar,activation.jar等相关jar包在lib文件夹下。
jaxb-api jaxb-impl jar
JAXB的安装包及插件
jaxb解析生成xml例子
Jaxb annotation 使用 Jaxb annotation初步使用
JAXB的 eclipse插件 JAXB的 eclipse插件 JAXB的 eclipse插件 JAXB的 eclipse插件 JAXB的 eclipse插件
在使用webservice,mule esb等需要jaxb的项目里经常会出现 JAXB 2.0 API is being loaded from the bootstrap classloader这个错误,按照打出的信息Use the endorsed directory mechanism to place jaxb-api.jar in ...
java -jar JAXB2_20060607.jar On Windows, you can just double-click the jar file to execute. Release Notes Browse the release notes online, including what's new. Technical Support Please subscribe to...
JAXB注解相关技术JAXB注解相关技术JAXB注解相关技术JAXB注解相关技术JAXB注解相关技术JAXB注解相关技术
用jaxb自动生成xsd对像的实例,简单易用
使用jaxb生成XML例子,含有例子和注解解析
有关Maven项目中缺少jaxb-api的异常报错解决,jaxb-api-2.3.0.jar
一个JAXB2的基础教程,教你快速运用JAXB通过XSD绑定XML和读写XML
jaxb-2.2.jar jaxb jax
jaxb-api jaxb-impl jaxb-xjc jaxws-rt 这四个文件对应的jar包
现在很多maven仓库均没有此jar jaxb-api-2.3.0.jar jaxb-core-2.3.0.jar
赠送jar包:jackson-module-jaxb-annotations-2.2.3.jar; 赠送原API文档:jackson-module-jaxb-annotations-2.2.3-javadoc.jar; 赠送源代码:jackson-module-jaxb-annotations-2.2.3-sources.jar; 赠送Maven依赖...
jaxb最新版本2.2.6
有关Maven项目中缺少jaxb-api的异常报错解决,jaxb-core-2.3.0.jar