前言
开发中碰到与三方系统对接,而对方使用XML格式进行数据接收,我方采用JAXB进行XML数据的格式化。在使用JAXB进行对象转XML时,不可避免的对实体对象进行了通用属性的提取和子类的继承,在此过程中,发现JAXB并不会对子类属性进行格式化。
问题复现
&emsp新建三个类,ResourceLibrary、Resource和FileResource,其中FileResource为Resource的子类,类结构如下:
ResourceLibrary
@XmlRootElement(name = "resource-library")
public class ResourceLibrary {
private String libraryName;
private List<Resource> resources;
public String getLibraryName() {
return libraryName;
}
public void setLibraryName(String libraryName) {
this.libraryName = libraryName;
}
public List<Resource> getResources() {
return resources;
}
public void setResources(List<Resource> resources) {
this.resources = resources;
}
}
Resource
public abstract class Resource {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
FileResource
public class FileResource extends Resource {
private String path;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}
测试复现
新建测试类,对 ResourceLibrary 进行实例化和XML格式化。
public class JaxbDome {
public static void main(String[] args) throws JAXBException {
ResourceLibrary library = new ResourceLibrary();
library.setLibraryName("资源库");
FileResource resource1 = new FileResource();
resource1.setName("文件资源1");
resource1.setPath("文件路径1");
FileResource resource2 = new FileResource();
resource2.setName("文件资源2");
resource2.setPath("文件路径2");
library.setResources(Arrays.asList(resource1, resource2));
JAXBContext jaxbContext = JAXBContext.newInstance(ResourceLibrary.class);
Marshaller marshaller = jaxbContext.createMarshaller();
// 格式化结果,便于查看
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(Marshaller.JAXB_ENCODING, "utf8");
StringWriter writer = new StringWriter();
marshaller.marshal(library, writer);
System.out.println(writer.toString());
}
}
运行结果如下:
<resource-library>
<libraryName>资源库</libraryName>
<resources>
<name>文件资源1</name>
</resources>
<resources>
<name>文件资源2</name>
</resources>
</resource-library>
发现 FileResource 中的 path 属性并未格式化。
JAXBContext.newInstance(ResourceLibrary.class) 所获取 JAXBContext 上下文中并未有 FileResource 类信息和对应格式化内容,因为 ResourceLibrary 中仅包含有 Resource 相关类信息,所以在格式化的过程中 FileResource 实例只会被当做是 Resource 实例进行属性的读取和格式化操作。
问题解决
根据上述问题,发现XmlSeeAlso 注解可解决。查看 XmlSeeAlso 源码为:
/**
* Instructs JAXB to also bind other classes when binding this class.
*
* <p>
* Java makes it impractical/impossible to list all sub-classes of
* a given class. This often gets in a way of JAXB users, as it JAXB
* cannot automatically list up the classes that need to be known
* to {@link JAXBContext}.
*
* <p>
* For example, with the following class definitions:
*
* <pre>
* class Animal {}
* class Dog extends Animal {}
* class Cat extends Animal {}
* </pre>
*
* <p>
* The user would be required to create {@link JAXBContext} as
* <tt>JAXBContext.newInstance(Dog.class,Cat.class)</tt>
* (<tt>Animal</tt> will be automatically picked up since <tt>Dog</tt>
* and <tt>Cat</tt> refers to it.)
*
* <p>
* {@link XmlSeeAlso} annotation would allow you to write:
* <pre>
* @XmlSeeAlso({Dog.class,Cat.class})
* class Animal {}
* class Dog extends Animal {}
* class Cat extends Animal {}
* </pre>
*
* <p>
* This would allow you to do <tt>JAXBContext.newInstance(Animal.class)</tt>.
* By the help of this annotation, JAXB implementations will be able to
* correctly bind <tt>Dog</tt> and <tt>Cat</tt>.
*
* @author Kohsuke Kawaguchi
* @since JAXB2.1
*/
@Target({ElementType.TYPE})
@Retention(RUNTIME)
public @interface XmlSeeAlso {
Class[] value();
}
源码中说明:Java编码中是不可能穷举所有子类,如源码示例中JAXBContext.newInstance(Animal.class) 此句中,JAXBContext 无法自动的获取 Animal 的所有子类,也就无法对子类实例中的属性进行格式化处理,所以需要显式地告知 JAXBContext 可以处理的 Animal 子类。所以就有了 XmlSeeAlso 注解,可以在 Animal 类上标记此类的子类,JAXBContext 就可以绑定处理这些子类的实例。
Resource代码调整:
@XmlSeeAlso({
FileResource.class
})
public abstract class Resource {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
运行结果如下:
<resource-library>
<libraryName>资源库</libraryName>
<resources xsi:type="fileResource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<name>文件资源1</name>
<path>文件路径1</path>
</resources>
<resources xsi:type="fileResource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<name>文件资源2</name>
<path>文件路径2</path>
</resources>
</resource-library>
结果正确,问题解决!