JAXB2.0之日常使用

    说起我的开发之路,就是一段段的悲剧组成的:刚开始使用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")
@XmlElement(name = "item")
List<Item> list;

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类,然后手工在两个类间转换吧。


你可能感兴趣的:(JAXB2.0之日常使用)