说起我的开发之路,就是一段段的悲剧组成的:刚开始使用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类,然后手工在两个类间转换吧。