说起我的开发之路,就是一段段的悲剧组成的:刚开始使用Flex的时候,苹果不支持Flash了;刚弄懂了SOAP、SCA和SOA,然后REST就火了;刚学会scala,然后java8就出来了……
好在现在已经开始使用REST,希望能赶上这班车,自己的这点浅薄知识还能混口饭吃。
说起REST,现在数据表示可能JSON居多,但XML也不可忽视,现在好多第三方API都同时提供对JSON和XML的支持。前段时间做微信公众号,竟然一部分API用JSON,一部分用XML,这让我对他们的能力表示怀疑。
为什么选择JAXB呢?我们的REST服务是用spring MVC3来做的,自带的消息转换功能很强悍,它封装了对JSON、XML、TEXT等常用格式的支持,只需@ResponseBody和@RequestBody注解就能自动做java到所需格式的转换,很是方便。JSON转换使用jackson。数据映射需要自己设置一些映射规则,JAXB在类上加注解这种方式还是挺方便,再者它的注解还能和jakson共享,这没理由不选择它了。
一、什么是JAXB
从字面上就能理解,它是java和XML绑定的意思,注意,不是“映射“。它是J2EE里Webservice规范的一部分,在传统的SOAP服务里也在使用。绑定指的是java类和XML schema绑定,在使用REST时我们在定义XML数据格式的时候很少显示的定义schema,JAXB在做对象和XML转换的时候,XML schema相当于中间产物,我们也已很少关心了,但毕竟JAXB是一个规范,内容还是很严谨和丰富的。
二、从java到xml
简而言之,我们已经有了java类,然后定义XML的格式.下面这个例子通过在类上添加注解定义了XML的生成规则。
@XmlRootElement(name = "wsUser") @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name="wsUser",propOrder = {"userId", "userName", "birthday", "description"}) public class WsUser implements Serializable { private Long userId; @XmlElement(name = "user") private String userName; @XmlJavaTypeAdapter(DateAdapter.class) private Date birthday; private String description; //getter setter…… }
各个注解的含义,有些注解会随后用到:
注解名 | 含义 | 说明 |
XmlRootElement | 表示该类是XML根元素 | name属性定义了XML根元素的元素名 |
XmlAccessorType | 元素访问类型 | XmlAccessType.FIELD表示该类里除了有XmlTransient注解外所有属性在生成XML时都会使用 |
XmlType | 定义XML的生成规则 | name是指生成的schema的名字,可以不写;propOrder 属性在xml里的出现顺序 |
XmlElement | 定义XML元素的生成规则 | name属性指定类属性在XML里的元素名 |
XmlElementWrapper | 定义XML元素被包装的元素信息 | 如: @XmlElementWrapper(name="items") |
XmlJavaTypeAdapter | 自定义转换规则 | 指定自定义转换的类 |
调用JAXB来作转换也比较简单,JDK6以后已经把JAXB集成了进来,所以不需要第三方jar包即可实现转换,代码如下:
File file = new File("D:\\result.xml"); JAXBContext context = JAXBContext.newInstance(WsUser.class); //构建对象 WsUser wsUser=new WsUser(); //………… //java转XML Marshaller marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); marshaller.marshal(wsUser, file);//file是生成的XML保存路径 //XML转java Unmarshaller unmarshaller = context.createUnmarshaller(); WsUser wsUser2 = (WsUser) unmarshaller.unmarshal(file);
下面是一些例子:
1.XML简单转换
代码如上面第一段的片断,生成的XML数据:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><wsUser><userId>1</userId><userName>name1</userName><birthday>2014-04-16 02:07:06</birthday><description>name1</description></wsUser>
2.类组合的转换
代码如下:
@XmlRootElement(name = "wsUser") @XmlAccessorType(XmlAccessType.FIELD) @XmlType(propOrder = {"userId", "userName", "birthday", "description", "dept"}) public class WsUser implements Serializable { private Long userId; private String userName; @XmlJavaTypeAdapter(DateAdapter.class) private Date birthday; private String description; private WsDept dept; //getter setter…… } @XmlAccessorType(XmlAccessType.FIELD) public class WsDept { private String deptName; @XmlJavaTypeAdapter(DateAdapter.class) private Date createDate; private String provinceName; //getter setter…… }
生成的XML数据:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><wsUser><userId>1</userId><userName>name1</userName><birthday>2014-04-16 02:05:55</birthday><description>name1</description><dept><deptName>北京</deptName><createDate>2014-04-16 02:05:55</createDate><provinceName>北京</provinceName></dept></wsUser>
3.包含集合的转换
在生成XML数据时,集合元素需要有一个根元素,所以如果要直接返回集合类数据时,需要定义一个类来拥有它:
@XmlRootElement(name = "users") @XmlAccessorType(XmlAccessType.FIELD) public class WsUserList { @XmlElement(name = "user") private List<WsUser> users; public List<WsUser> getUsers() { if (users == null) users = new ArrayList<WsUser>(); return users; } }
生成的XML数据:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><users><user><userId>1</userId><userName>name1</userName><birthday>2014-04-16 02:09:03</birthday><description>name1</description></user><user><userId>2</userId><userName>name2</userName><birthday>2014-04-16 02:09:03</birthday><description>name2</description></user></users>
如果一个类中除集合类外还包含其他元素,那可以使用XmlElementWrapper注解来定义包裹元素,避免上面代码多定义一层类的缺点,代码如下:
@XmlRootElement(name = "deptInfo") @XmlAccessorType(XmlAccessType.FIELD) @XmlType(propOrder = {"deptName", "users"}) public class DepartInfo { @XmlElement(name = "deptname") private String deptName; @XmlElementWrapper(name = "deptusers") @XmlElement(name = "user") private List<WsUser> users; public List<WsUser> getUsers() { if (users == null) users = new ArrayList<WsUser>(); return users; } public String getDeptName() { return deptName; } public void setDeptName(String deptName) { this.deptName = deptName; } }
生成的XML数据:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><deptInfo><deptname>testDept</deptname><deptusers><user><userId>1</userId><userName>name1</userName><birthday>2014-04-16 02:15:23</birthday><description>name1</description></user><user><userId>2</userId><userName>name2</userName><birthday>2014-04-16 02:15:23</birthday><description>name2</description></user></deptusers></deptInfo>
4.枚举的转换
java代码:
//使用enum的类 @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "abstractMessage") @XmlRootElement public class AbstractMessage { @XmlElement(name = "touser") private String toUser; @XmlElement(name = "msgtype") private MessageContentType msgType; //getter and setter…… } //enum的定义 @XmlEnum public enum MessageContentType { @XmlEnumValue("text") TEXT("text"), @XmlEnumValue("image") IMAGE("image"), @XmlEnumValue("news") NEWS("news"); private final String value; MessageContentType(String value) { this.value = value; } public static MessageContentType fromValue(String v) { for (MessageContentType c: MessageContentType.values()) { if (c.value.equals(v)) { return c; } } throw new IllegalArgumentException(v); } }
生成的XML数据:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><abstractMessage><touser>22222222</touser><msgtype>text</msgtype></abstractMessage>
5.继承关系的转换
直接贴代码了,逻辑很简单,就不解释了。这是我定义的微信消息发送类,公用抽象类:
@XmlAccessorType(XmlAccessType.FIELD) public class AbstractMessage { @XmlElement(name = "touser") private String toUser; @XmlElement(name = "msgtype") private MessageContentType msgType; //getter and setter }
文本信息发送类:
@XmlRootElement(name = "xml") @XmlAccessorType(XmlAccessType.FIELD) public class TextMessageNew extends AbstractMessage{ @XmlElement(name = "text") private TextMessageContent content; public TextMessageNew() { setMsgType(MessageContentType.TEXT); } //getter and setter } @XmlAccessorType(XmlAccessType.FIELD) public class TextMessageContent { @XmlElement(name = "content") private String content; //getter and setter }
图片信息发送类:
@XmlRootElement(name = "xml") @XmlAccessorType(XmlAccessType.FIELD) public class ImageMessageNew extends AbstractMessage{ @XmlElement(name = "image") private ImageMessageContent content; public ImageMessageNew() { setMsgType(MessageContentType.IMAGE); } //getter and setter } @XmlAccessorType(XmlAccessType.FIELD) public class ImageMessageContent { @XmlElement(name = "media_id") private String content; //getter and setter }
类结构还是有点不太满意,如果能去掉Message消息类里内嵌的MessageContent类定义,合而为一就好了。没找到能实现该功能的注解,自定义XmlJavaTypeAdapter转换又太麻烦,就这样吧……
文本信息发送类生成的XML数据:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><xml><touser>eeeeeeeeeeeeeee</touser><msgtype>text</msgtype><text><content>内容这是</content></text></xml>
图片信息发送类生成的XML数据:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><xml><touser>eeeeeeeeeeeeeee</touser><msgtype>image</msgtype><image><media_id>mafs40J</media_id></image></xml>
6.二进制文件的转换
在传输图片或文件的时候可能会用到,至于传java序列化的数据,还是算了吧,用XML就为了跨语言,java序列化的数据你让其他语言怎么解?一般是把二进制数据显示为String传输,有两种形式,一种是直接把二进制数据显示为String使用,还有一种是把String再作一次base64.
第一种:
@XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "", propOrder = { "data" }) @XmlRootElement(name = "JavaObjectType") public class JavaObjectType { @XmlElement(required = true, type = String.class) @XmlJavaTypeAdapter(HexBinaryAdapter.class) @XmlSchemaType(name = "hexBinary") protected byte[] data; //getter and setter }
这里的HexBinaryAdapter在JAXB已经实现。
使用base64处理数据,XmlJavaTypeAdaper需要自己实现:
public final class Base64Adapter extends XmlAdapter<String, byte[]> { public byte[] unmarshal(String s) { if (s == null) return null; return org.apache.commons.codec.binary.Base64.decodeBase64(s); } public String marshal(byte[] bytes) { if (bytes == null) return null; return org.apache.commons.codec.binary.Base64.encodeBase64String(bytes); } } @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "", propOrder = { "data" }) @XmlRootElement(name = "JavaObjectType") public class JavaObjectType { @XmlElement(required = true, type = String.class) @XmlJavaTypeAdapter(Base64Adapter.class) @XmlSchemaType(name = "base64Binary") protected byte[] data; //getter and setter }
测试代码之java转XML:
//读入图片 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); FileInputStream intputStream = new FileInputStream(new File("D:/imge1.jpg")); byte[] buf = new byte[1024 * 10]; int read; while ((read = intputStream.read(buf)) != -1) { outputStream.write(buf,0,read); } intputStream.close(); //组装java对象 byte[] bytes = outputStream.toByteArray(); JavaObjectType jot = of.createJavaObjectType(); jot.setData(bytes); outputStream.close(); //java对象转XML File file = new File("D:\\javaObject4_4.xml"); JAXBContext context = JAXBContext.newInstance(JavaObjectType.class); Marshaller marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); marshaller.marshal(jot, file);
测试代码之XML转java:
File file = new File("D:\\javaObject4_4.xml"); JAXBContext context = JAXBContext.newInstance(JavaObjectType.class); Unmarshaller unmarshaller = context.createUnmarshaller(); JavaObjectType newJot = (JavaObjectType) unmarshaller.unmarshal(file); //保存到文件 byte[] datas = newJot.getData(); ByteArrayInputStream bis = new ByteArrayInputStream(datas); FileOutputStream fileOutputStream = new FileOutputStream(new File("D:/img2.jpg")); int read; byte[] buf = new byte[1024 * 10]; while ((read = bis.read(buf)) != -1) { fileOutputStream.write(buf,0,read); } fileOutputStream.close();
生成的XML数据:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <JavaObjectType> <data>此处略去一万字……</data> </JavaObjectType>
XmlJavaTypeAdapter使用方法
上面代码中@XmlJavaTypeAdapter(DateAdapter.class)定义的实现如下,需要继承XmlAdapter类,第一个类参数是转换后的类型,第二个参数是要转换的类型。
public class DateAdapter extends XmlAdapter<String, Date> { private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public String marshal(Date v) throws Exception { return dateFormat.format(v); } @Override public Date unmarshal(String v) throws Exception { return dateFormat.parse(v); } }
另,根据java对象生成XML Schema的代码:
JAXBContext context = JAXBContext.newInstance(WsUser.class); final List results = new ArrayList(); // generate the schema context.generateSchema( // need to define a SchemaOutputResolver to store to new SchemaOutputResolver() { @Override public Result createOutput( String ns, String file )throws IOException{ // save the schema to the list DOMResult result = new DOMResult(); result.setSystemId( file ); results.add( result ); return result; } } ); // output schema via System.out DOMResult domResult = (DOMResult) results.get( 0 ); Document doc = (Document) domResult.getNode(); OutputFormat format = new OutputFormat( doc ); format.setIndenting( true ); XMLSerializer serializer = new XMLSerializer( System.out, format ); serializer.serialize( doc );
三、从xml到java
如果你已经定义了一份XML schema文件,可以执行命令生成java类,这个功能还是挺强大的,估计用处不多。
在命令行执行:
xjc -p 生成类的包名 schema文件url或schema文件路径
四、从xml和java一起开始
这种方式应该是最常遇到的,比如我调用别人的接口,人家已经定义了XML规范,我也有我的java类,那就在java类上加注解来调和吧,生成的结果试到和人家规范一致就算成功。如果实在差距太大,就定义一个和XML规范兼容的java类,然后手工在两个类间转换吧。