JAXB marshall/unmarshall 由插件定义的对象

JAXB marshall/unmarshall objects defined by plugins

让我们假设以下场景:

我有以下类型的对象列表:

public class MyObject {
   private String name
   private SomeClass someField
   private List<Fact> facts
}

字段namesomeField只是为了表明class有一些固定成员。您可以假设知道如何将这些 classes 转换为 xml.

Fact 是一个接口,其中的实现我不知道,但由插件提供。可以要求插件提供任意代码,但我希望它尽可能简单。

我想将这些对象保存并加载到 xml。请注意,在加载 xml 时,并非所有的实现都存在(xml 可能是用一组不同的插件编写的)。我希望能够继续阅读 xml 并且在再次保存时不会丢失任何信息。换句话说:我愿意将 List<Element>List<String> 之类的字段添加到 class 并且在读取 xml 时,所有存在插件的部分都应该是读入相应的 Facts,而所有没有插件的部分都应存储在 ElementString 中,再次保存时,两个列表都会被保存并可以被具有的程序读取所有插件。

如何最好地使用 JAXB 实现这一点?

我能看到的一种方法是使用 Map<Class, org.w3c.dom.Element> 而不是 List<Fact> ,它可以由 JaxB 转换为 xml 然后让任何插件提供自定义代码从 "their"元素使用org.w3c.domAPI,但是使用那个API有点麻烦,请问有没有更好的方法?

不知道 best,但一种接近您描述的方法是:

JAXB 不适用于接口;它能做的最好的事情是抽象 class。这意味着您需要使用 List<Object>List<AbstractFact>。 (但您可以在 getter、pluginresolver 或 afterUnmarshall() 中实施一些限制)。

您的插件为扩展提供了基本的 classes(SPI 是通常的方法)。您收集它们并(在验证后)使用它们来创建您的 JAXBContext。 (如果你想支持多个接口,也许可以通过不同的方式提供)。

在 xml 中,您需要有这样的类型标记:<fact xsi:type=\"aFact\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">。如果您使用 jaxb 创建 xml ,它将自动创建。 (classes 需要有@XmlRootElement 注解)。

这是一个精简的例子:

interface Fact {

}
@XmlRootElement
@XmlAccessorType(XmlAccessType.NONE)
class R {

    @XmlElement(name = "fact")
    private List<Object> facts;


    @SuppressWarnings("unchecked")
    public List<Fact> getTest() {
        if (facts == null) {
            facts = new ArrayList<>();
        }
        return (List<Fact>) (Object) facts;
    }

    public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
        // check if all facts implement same interface
        for(Object object:facts) {
            if (!(object instanceof Fact)) {
                throw new IllegalArgumentException("Unsupported type in facts list");
            }
        }
    }
}

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "aFact")
class AFact implements Fact {

    @XmlElement
    private String a;

    public AFact() {
    }

    public AFact(String a) {
        this.a = a;
    }

    public String getA() {
        return a;
    }

    @Override
    public String toString() {
        return "AFact [a=" + a + "]";
    }

}

public class Jax {
    public static void main(String[] args) throws JAXBException {

        String xml = "<r><fact xsi:type=\"aFact\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><a>ba</a></fact></r>";

        List<Class<?>> contextClasses = new ArrayList<>();
        contextClasses.add(R.class);
        contextClasses.addAll(getClassesFromPlugin());
        JAXBContext context = JAXBContext.newInstance(contextClasses.toArray(new Class<?>[0]));
        R entity = (R) context.createUnmarshaller().unmarshal(new StringReader(xml));

        System.out.println(entity.getTest());

        R r = new R();
        r.getTest().add(new AFact("ab"));

        context.createMarshaller().marshal(r, System.out);
    }

    private static List<Class<?>> getClassesFromPlugin() {
        List<Class<?>> asList = Arrays.asList(AFact.class);
        for(Class<?> cls:asList) {
            if (!Fact.class.isAssignableFrom(cls)) {
                throw new IllegalArgumentException("Unsupported class");
            }
        }
        return asList;
    }
}