1、基本介绍
在工作中,经常为了调通上游接口,从而对请求第三方的参数进行XML序列化,这里常使用的方式就是使用JAVA扩展包中的相关注解和类来实现xml的序列化和反序列化。
2、自定义工具类
java">
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.concurrent.ConcurrentHashMap;
/**
* 1、实现 对象 转 xml
* 2、实现 xml 转对象
*/
public class XmlInterfaceUtils {
private static final ConcurrentHashMap<Class<?>, JAXBContext> contextMap =
new ConcurrentHashMap<>();
private static JAXBContext context(Class<?> clazz) {
// JAXBContext 是线程安全的,可以在多个线程中复用
// computeIfAbsent 方法,如果map集合存在相同的key,则覆盖value值;不存在相同key,则添加到map集合中
return contextMap.computeIfAbsent(clazz, cls -> {
try {
return JAXBContext.newInstance(cls);
} catch (JAXBException e) {
throw new IllegalStateException(e);
}
});
}
public static String convertToXml(Object obj) {
StringWriter sw = new StringWriter();
JAXBContext context = context(obj.getClass());
Marshaller marshaller;
try {
marshaller = context.createMarshaller();
//1.格式化输出,即按标签自动换行,否则就是一行输出
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
Boolean.TRUE);
//2.设置编码(默认编码就是utf-8)
// marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
//3.是否省略xml头信息,默认不省略(false)
// <?xml version="1.0" encoding="UTF-8"> 这一句就是"头信息"
// marshaller.setProperty(Marshaller.JAXB_FRAGMENT, false);
marshaller.marshal(obj, sw);
} catch (JAXBException e) {
throw new IllegalStateException(e);
}
return sw.toString();
}
/**
* xml转object
*
* @param clazz 转换类
* @param xml XML 字符串
* @param <T> 对象类型
* @return 转换结果
*/
public static <T> T xmlToObject(Class<T> clazz, String xml) {
JAXBContext context = context(clazz);
// 每次都创建 Unmarshaller
Unmarshaller unmarshaller;
try {
unmarshaller = context.createUnmarshaller();
} catch (JAXBException e) {
throw new IllegalStateException(e);
}
StringReader reader = new StringReader(xml);
T message;
try {
message = (T) unmarshaller.unmarshal(reader);
} catch (JAXBException e) {
throw new IllegalStateException(e);
}
return message;
}
}
3、模拟请求第三方的请求参数-V1.0
3.1 定义业务实体
Provider类
java">import javax.xml.bind.annotation.*;
@XmlRootElement
public class Provider {
private User user;
private String id;
private Integer providerTelephone;
private String providerAddress;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Integer getProviderTelephone() {
return providerTelephone;
}
public void setProviderTelephone(Integer providerTelephone) {
this.providerTelephone = providerTelephone;
}
public String getProviderAddress() {
return providerAddress;
}
public void setProviderAddress(String providerAddress) {
this.providerAddress = providerAddress;
}
}
User类
java">public class User {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
3.2 运行代码
java">
public class Application {
public static void main(String[] args) {
Provider provider = new Provider();
User user = new User();
user.setUsername("hu");
user.setPassword("123456");
provider.setUser(user);
provider.setProviderTelephone(4008123);
provider.setProviderAddress("BeiJing");
provider.setId("No.1");
//序列化成xml格式的字符串
String xml = XmlInterfaceUtils.convertToXml(provider);
System.out.println(xml);
//反序列化成对象
Provider provider1 = XmlInterfaceUtils.xmlToObject(Provider.class, xml);
}
}
控制台打印结果
必须要有一个@XmlRootElement用来标记哪个类作为根节点。否则,反序列化会失败,提示缺少 @XmlRootElement注解。
4、模拟请求第三方的请求参数-V2.0
假如第三方发生改变,要求我们进行适配。
将Provider类原本的id标签设置为根节点的属性,其他标签全部首字母大写,且按照手机号码,地址,用户信息的顺序进行反序列化,而User类的标签仍然是小写开头。
java">mport javax.xml.bind.annotation.*;
@XmlType(
//指定序列化的时候,生成每个标签的顺序,不指定的话,默认按照从上到下的顺序生成
propOrder = {"providerTelephone", "providerAddress", "user","id"}
)
@XmlRootElement(name = "Provider")
@XmlAccessorType(XmlAccessType.FIELD)
public class Provider {
@XmlElement(name = "User")
private User user;
//该字段映射为一个属性
@XmlAttribute(name = "id")
private String id;
@XmlElement(name = "ProviderTelephone")
private Integer providerTelephone;
@XmlElement(name = "ProviderAddress")
private String providerAddress;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Integer getProviderTelephone() {
return providerTelephone;
}
public void setProviderTelephone(Integer providerTelephone) {
this.providerTelephone = providerTelephone;
}
public String getProviderAddress() {
return providerAddress;
}
public void setProviderAddress(String providerAddress) {
this.providerAddress = providerAddress;
}
}
运行结果如下
5、@XmlAccessorType的作用
通过上面的例子可以发现,@XmlElement注解用来是生成子节点,@XmlAttribute注解用来生成节点的属性。
那@XmlAccessorType注解的作用呢?
默认序列化的时候,会根据类的get()方法生成一个子节点或者是属性,但是,我在字段名上又用@XmlElement标记了,这也会生出一个子节点。两个相同的子节点名称,就会导致反序列化失败。
因此,就需要用【 @XmlAccessorType(XmlAccessType.FIELD) 】来直接对类的字段进行映射,不考虑get方法,这样就会正常序列化。