JAXB marshall/unmarshall 由插件定义的对象
JAXB marshall/unmarshall objects defined by plugins
让我们假设以下场景:
我有以下类型的对象列表:
public class MyObject {
private String name
private SomeClass someField
private List<Fact> facts
}
字段name
和someField
只是为了表明class有一些固定成员。您可以假设知道如何将这些 classes 转换为 xml.
Fact
是一个接口,其中的实现我不知道,但由插件提供。可以要求插件提供任意代码,但我希望它尽可能简单。
我想将这些对象保存并加载到 xml。请注意,在加载 xml 时,并非所有的实现都存在(xml 可能是用一组不同的插件编写的)。我希望能够继续阅读 xml 并且在再次保存时不会丢失任何信息。换句话说:我愿意将 List<Element>
或 List<String>
之类的字段添加到 class 并且在读取 xml 时,所有存在插件的部分都应该是读入相应的 Fact
s,而所有没有插件的部分都应存储在 Element
或 String
中,再次保存时,两个列表都会被保存并可以被具有的程序读取所有插件。
如何最好地使用 JAXB 实现这一点?
我能看到的一种方法是使用 Map<Class, org.w3c.dom.Element>
而不是 List<Fact>
,它可以由 JaxB 转换为 xml 然后让任何插件提供自定义代码从 "their"元素使用org.w3c.dom
API,但是使用那个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;
}
}
让我们假设以下场景:
我有以下类型的对象列表:
public class MyObject {
private String name
private SomeClass someField
private List<Fact> facts
}
字段name
和someField
只是为了表明class有一些固定成员。您可以假设知道如何将这些 classes 转换为 xml.
Fact
是一个接口,其中的实现我不知道,但由插件提供。可以要求插件提供任意代码,但我希望它尽可能简单。
我想将这些对象保存并加载到 xml。请注意,在加载 xml 时,并非所有的实现都存在(xml 可能是用一组不同的插件编写的)。我希望能够继续阅读 xml 并且在再次保存时不会丢失任何信息。换句话说:我愿意将 List<Element>
或 List<String>
之类的字段添加到 class 并且在读取 xml 时,所有存在插件的部分都应该是读入相应的 Fact
s,而所有没有插件的部分都应存储在 Element
或 String
中,再次保存时,两个列表都会被保存并可以被具有的程序读取所有插件。
如何最好地使用 JAXB 实现这一点?
我能看到的一种方法是使用 Map<Class, org.w3c.dom.Element>
而不是 List<Fact>
,它可以由 JaxB 转换为 xml 然后让任何插件提供自定义代码从 "their"元素使用org.w3c.dom
API,但是使用那个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;
}
}