记-使用JAXB进行对象转XML时的实体继承问题

开发记录-使用JAXB进行对象转XML时的实体继承问题

前言

 开发中碰到与三方系统对接,而对方使用XML格式进行数据接收,我方采用JAXB进行XML数据的格式化。在使用JAXB进行对象转XML时,不可避免的对实体对象进行了通用属性的提取和子类的继承,在此过程中,发现JAXB并不会对子类属性进行格式化。

问题复现

&emsp新建三个类,ResourceLibraryResourceFileResource,其中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>
 * &#64;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>

结果正确,问题解决!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值