jaxb 实现 soap.xml 转 java bean

xml 具体示例如下:

 <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <PostBikeInfoResponse xmlns="http://tempuri.org/">
      <PostBikeInfoResult>
        <Code>int</Code>
        <Message>string</Message>
      </PostBikeInfoResult>
    </PostBikeInfoResponse>
  </soap:Body>
</soap:Envelope>

对应的 java bean类,我是这样编写的:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="soap:Envelope")
public class ResponseEntity {

@XmlAttribute(name="xmlns:xsi")
private String xsi;
@XmlAttribute(name="xmlns:xsd")
private String xsd;
@XmlAttribute(name="xmlns:soap")
private String soap;
@XmlElement(name="soap:Body")
private ResponseBody ResponseBody;
......

问题:

 javax.xml.bind.UnmarshalException: 意外的元素 (uri:"http://schemas.xmlsoap.org/soap/envelope/", local:"Envelope")。所需元素为<{http://tempuri.org/}PostBikeInfoResponse>,<{}PostBikeInfoResult>,<{}soap:Body>,<{}soap:Envelope>

具体错误的原因是什么?

因为jaxb 实现 soap.xml 转 java bean ,

package com.aiait.ivs.util;

import java.io.StringReader;
import java.io.StringWriter;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

/**

  • Jaxb工具类 xml和java类相互转换 *
  • @author sunjianbo
  • @date 2017年4月17日
    */
    public class JaxbXmlUtil {

    public static final String DEFAULT_ENCODING = "UTF-8";

    /**

    • pojo转换成xml 默认编码UTF-8 *
    • @param obj 待转化的对象
    • @return xml格式字符串
    • @throws Exception JAXBException */ public static String convertToXml(Object obj) throws Exception { return convertToXml(obj, DEFAULT_ENCODING); }

    /**

    • pojo转换成xml *
    • @param obj 待转化的对象
    • @param encoding 编码
    • @return xml格式字符串
    • @throws Exception JAXBException
      */
      public static String convertToXml(Object obj, String encoding) throws Exception {
      String result = null;

      JAXBContext context = JAXBContext.newInstance(obj.getClass());
      Marshaller marshaller = context.createMarshaller();
      // 指定是否使用换行和缩排对已编组 XML 数据进行格式化的属性名称。
      marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
      marshaller.setProperty(Marshaller.JAXB_ENCODING, encoding);

      StringWriter writer = new StringWriter();
      marshaller.marshal(obj, writer);
      result = writer.toString();

      return result;
      }

    /**

    • xml转换成JavaBean *
    • @param xml xml格式字符串
    • @param t 待转化的对象
    • @return 转化后的对象
    • @throws Exception JAXBException */ @SuppressWarnings("unchecked") public static T convertToJavaBean(String xml, Class t) throws Exception { T obj = null; JAXBContext context = JAXBContext.newInstance(t); Unmarshaller unmarshaller = context.createUnmarshaller(); obj = (T) unmarshaller.unmarshal(new StringReader(xml)); return obj; } } 复制代码 生成xml的实体类

复制代码
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"header",
"body"
})
@XmlRootElement(name = "soapenv:Envelope")
public class RequestEntity {

@XmlAttribute(name="xmlns:soapenv")
protected String soapenv="http://schemas.xmlsoap.org/soap/envelope/";
@XmlAttribute(name="xmlns:eapp")
protected String eapp="http://wx.gmw9.com";

@XmlAttribute(name="xmlns:bus")
protected String bus=null;



@XmlElement(required = true,name="soapenv:Header")
protected RequestHeader header;

@XmlElement(required = true,name="soapenv:Body")
protected RequestBody body;


//get set方法省略

}

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "soapenv:Body")
public class RequestBody {

@XmlElement(required = true,name="eapp:aaaaaa")
public BodyContent aaaaaaa
//get set方法省略

}

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"aa1",
"aa2",
"aa3"

})
@XmlRootElement(name = "eapp:aaaaaa")
public class BodyContent {

      @XmlElement(required = true,name="eapp:compNo")
    protected String aa1;
    @XmlElement(required = true,name="eapp:agentCode")
    protected String aa2;
    @XmlElement(required = true,name="eapp:eAppId")
    protected String aa3;              
    //get set方法省略

}
复制代码
生成的xml如下

复制代码

soapenv:Header

/soapenv:Header
soapenv:Body
eapp:aaaaaa
eapp:aa1041/eapp:aa1
eapp:aa25000/eapp:aa2
eapp:aa3?/eapp:aa3

  </eapp:aaaaaa>

/soapenv:Body
/soapenv:Envelope

首先定义两个示例类ClassA,ClassB,用于后续的示例演示

public class ClassA {
private int classAId;
private String classAName;
private ClassB classB;

public int getClassAId() {
    return classAId;
}

public void setClassAId(int classAId) {
    this.classAId = classAId;
}

public String getClassAName() {
    return classAName;
}

public void setClassAName(String classAName) {
    this.classAName = classAName;
}

public ClassB getClassB() {
    return classB;
}

public void setClassB(ClassB classB) {
    this.classB = classB;
}

}

public class ClassB {
private int classBId;
private String classBName;

public int getClassBId() {
    return classBId;
}

public void setClassBId(int classBId) {
    this.classBId = classBId;
}

public String getClassBName() {
    return classBName;
}

public void setClassBName(String classBName) {
    this.classBName = classBName;
}

}
用于序列化的XmlUtil
import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.bind.*;

public class XmlUtil {
public static String toXML(Object obj) {
try {
JAXBContext context = JAXBContext.newInstance(obj.getClass());
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");// //编码格式
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);// 是否格式化生成的xml串
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, false);// 是否省略xm头声明信息
StringWriter writer = new StringWriter();
marshaller.marshal(obj, writer);
return writer.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

@SuppressWarnings("unchecked")
public static <T> T fromXML(String xml, Class<T> valueType) {
    try {
        JAXBContext context = JAXBContext.newInstance(valueType);
        Unmarshaller unmarshaller = context.createUnmarshaller();
        return (T) unmarshaller.unmarshal(new StringReader(xml));
    } catch (Exception e) {
        throw new RuntimeException(e.getMessage());
    }
}

}
调用如下:
public class MainRun {

/**
 * @param args
 */
public static void main(String[] args) {

    ClassB classB = new ClassB();
    classB.setClassBId(22);
    classB.setClassBName("B2");

    ClassA classA = new ClassA();
    classA.setClassAId(11);
    classA.setClassAName("A1");
    classA.setClassB(classB);

    System.out.println(XmlUtil.toXML(classA));
}

}
输出结果如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

11
A1

22
B2


这里要注意以下几点

要序列化的类加上 @XmlRootElement注解,否则会报错(错误提示很清晰,这里就不贴出来了)
JAXB序列化XML时 默认序列化getter和setter,且getter和setter必须成对出现才会被序列化
属性名称,默认序列化出来的类和属性名称默认是首字母转换为小写,若需要控制属性名称需要在getter或setter上使用 @XmlElement(name="ClassAId") 指定名称,这里要注意的是@XmlElement放置在getter或setter上都行,但只能放一个,也就是说不能同时在getter和setter上使用@XmlElement注解
如何控制根节点名称?
使用@XmlRootElement指定name属性即可,如@XmlRootElement(name="ClassA")
怎么添加命名空间
使用@XmlRootElement(namespace="cn.lzrabbit") 指定namespace属性
怎么精确控制每个属性名称
JAXB自动转化为首字母小写会导致不可预料的属性名称出现, 不嫌麻烦的话为每个属性设置@XmlElement(name=""),想省事的话使用Field
怎么样实现序列化时使用Field字段而不是使用setter和getter
在要使用的类上面加上@XmlAccessorType(XmlAccessType.FIELD)注解,并指定为XmlAccessType.FIELD,这里强烈推荐使用@XmlAccessorType(XmlAccessType.FIELD)注解,因为这样你可以精确的控制每个元素的名称,而不需要为每个属性去设置@XmlElement(name="")注解,当然也可以在Field上使用@XmlElement注解
下面给出使用了使用如上注解后的代码示例

@XmlRootElement(namespace="cn.lzrabbit")
@XmlAccessorType(XmlAccessType.FIELD)
public class ClassA {
private int classAId;

@XmlElement(name="ClassAName")
private String classAName;

private ClassB classB;

public int getClassAId() {
    return classAId;
}
public void setClassAId(int classAId) {
    this.classAId = classAId;
}

public String getClassAName() {
    return classAName;
}

public void setClassAName(String classAName) {
    this.classAName = classAName;
}

public ClassB getClassB() {
    return classB;
}

public void setClassB(ClassB classB) {
    this.classB = classB;
}

}

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class ClassB {
private int ClassBId;
private String ClassBName;

public int getClassBId() {
    return ClassBId;
}

public void setClassBId(int classBId) {
    this.ClassBId = classBId;
}

public String getClassBName() {
    return ClassBName;
}

public void setClassBName(String classBName) {
    this.ClassBName = classBName;
}

}
输出xml为
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

11
A1

22
B2

/ns2:classA
JAXB命名空间及命名空间前缀处理
使用package-info.java添加默认命名空间
在需要添加命名空间的包下面添加package-info.java文件,然后添加@XmlSchema注解,这样整个包序列化时就都会自动加上命名空间了
@XmlSchema(namespace = "http://www.lzrabbit.cn")
package cn.lzrabbit;

import javax.xml.bind.annotation.XmlSchema;

命名空间前缀处理
相信大名鼎鼎的ns2,nsXX让很多人非常头疼类似下面这样的
复制代码
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

11
A1

22
B2

/ns2:classA
复制代码
解决方法一(不推荐):
添加package-info.java添加@XmlSchema注解并设置属性xmlns

@XmlSchema( xmlns = { @XmlNs(namespaceURI = "http://www.lzrabbit.cn", prefix = "rabbit"), @XmlNs(namespaceURI = "http://www.cnblogs.com", prefix = "blog")})

package cn.lzrabbit;
import javax.xml.bind.annotation.XmlSchema;
import javax.xml.bind.annotation.XmlNs;
ClassA如下

复制代码
package cn.lzrabbit;

import javax.xml.bind.annotation.*;

@XmlRootElement(namespace="http://www.lzrabbit.cn")
@XmlAccessorType(XmlAccessType.FIELD)
public class ClassA {
private int classAId;

@XmlElement(name="ClassAName")
private String classAName;

private ClassB classB;

public int getClassAId() {
    return classAId;
}
public void setClassAId(int classAId) {
    this.classAId = classAId;
}

public String getClassAName() {
    return classAName;
}

public void setClassAName(String classAName) {
    this.classAName = classAName;
}

public ClassB getClassB() {
    return classB;
}

public void setClassB(ClassB classB) {
    this.classB = classB;
}

}
复制代码

序列化结果如下,可以看到已经按照我们所预期的修改了命名空间前缀,这里要注意下需要自定义前缀的实体类添加的@XmlRootElement(namespace="http://www.lzrabbit.cn")注解时指定的namespace必须和package-info.java定义的前缀一致,否则还是会生成nsXX这样的前缀

复制代码
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

11
A1

22
B2

/rabbit:classA
复制代码
注意事项
1.若jdk版本为1.6的需要需要添加jaxb-core-2.2.7.jar和jaxb-impl-2.2.7.jar两个包的引用,否则即便设置了package-info的XmlSchema注解的xmlns注释也不能生效,若为jdk 1.7的无需添加
2.使用XmlSchema定义的前缀会对整个包生效,无法实现对每个实体类的单独前缀定义,很不灵活,故此不推荐使用此方式

解决方法二(推荐):
同方法一若jdk版本为1.6需要添加jaxb-core-2.2.7.jar和jaxb-impl-2.2.7.jar两个包的引用,不过方法二不需要添加package-info当然也就不需要定义XmlSchema
思路就是实现NamespacePrefixMapper抽象类,并重写getPreferredPrefix方法,看到方法名应该都明白了,对就是在序列化的时候重写获取命名空间前缀方法,为了简洁这里使用类匿名内部类实现的

复制代码
marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new NamespacePrefixMapper() {
@Override
public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
if (namespaceUri.equals("http://www.lzrabbit.cn")) return "abc";
return suggestion;
}
});
复制代码
如上所示,在序列化时判断namespaceUri也就是我们定义的命名空间,然后返回我们自定义的前缀,其中的suggestion参数就是默认的前缀,有兴趣的话打印下就会发现suggestion就是ns2之类的前缀,把要自定义前缀的命名空间都在这里判断下就可以完全控制自定义前缀了,相对方法一来说可以实现对每个实体类的命名空间前缀控制,采用方法二后的序列化结果:

复制代码
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

11
A1

22
B2

/abc:classA
复制代码
采用方法二后的序列化方法

复制代码
package cn.lzrabbit;

import java.io.StringReader;
import java.io.StringWriter;

import javax.xml.bind.*;

import com.sun.xml.bind.marshaller.NamespacePrefixMapper;
import com.sun.xml.bind.v2.WellKnownNamespace;

public class XmlUtil {

public static String toXML(Object obj) {
    try {
        JAXBContext context = JAXBContext.newInstance(obj.getClass());

        Marshaller marshaller = context.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");// //编码格式
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);// 是否格式化生成的xml串
        marshaller.setProperty(Marshaller.JAXB_FRAGMENT, false);// 是否省略xm头声明信息

        marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new NamespacePrefixMapper() {
            @Override
            public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
                if (namespaceUri.equals("http://www.lzrabbit.cn")) return "abc";
                if (namespaceUri.contains("http://www.cnblogs.com")) return "blog";
                return suggestion;
            }
        });

        StringWriter writer = new StringWriter();
        marshaller.marshal(obj, writer);
        return writer.toString();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

@SuppressWarnings("unchecked")
public static <T> T fromXML(String xml, Class<T> valueType) {
    try {
        JAXBContext context = JAXBContext.newInstance(valueType);
        Unmarshaller unmarshaller = context.createUnmarshaller();
        return (T) unmarshaller.unmarshal(new StringReader(xml));
    } catch (Exception e) {
        throw new RuntimeException(e.getMessage());
    }
}

}
复制代码

现在我们基本解决了jaxb序列化xml的命名空间及前缀问题,但还是有很多问题,比如序列化和反序列化时如何忽略命名空间,如何使用@XmlRootElement控制每个实体类的默认命名空间也就是消除命名空间前缀

下一篇继续深入,Java XML操作之JAXB玩转命名空间

最后给下jaxb-core-2.2.7.jar和jaxb-impl-2.2.7.jar两个包的maven引用

复制代码

com.sun.xml.bind
jaxb-core
2.2.7


com.sun.xml.bind
jaxb-impl
2.2.7

复制代码
也可以自行去官网下载 https://jaxb.java.net/

http://blog.csdn.net/jclpc/article/details/48915541