在解组时调试问题的一个好方法是使用javax.xml.bind.helpers.DefaultValidationEventHandler。
在您的情况下,您可以简单地将其添加到 main 方法中的解组器中,例如
// ...
javax.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler());
// ...
当您在附加了验证处理程序的情况下按原样运行测试程序时,您会看到类似以下内容,这基本上是在告诉您没有与 XML 中的 test 元素对应的 Java 类。
[Exception [EclipseLink-25004] (Eclipse Persistence Services - 2.6.1.v20150916-55dc7c3): org.eclipse.persistence.exceptions.XMLMarshalException
Exception Description: An error occurred unmarshalling the document
Internal Exception: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 47; unexpected element (uri:"", local:"test"). Expected elements are (none)]
我认为您假设您的 ElementAdapter 实现将读取并使用 baz 节点和所有子节点作为字符串。实际上,我认为(分页 Blaise Doughan?)发生的事情是 JAXB 正在对 XML 树进行一些预遍历,在此期间它发现您没有 test 的模型,随后丢弃 test 及其属性。
要了解这里发生了什么,首先要简化您的模型,不要担心 JDOM 元素转换。请注意,我已经更改了您的顶级类的名称,因此它不会与 Test 模型类冲突:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.transform.stream.StreamSource;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
public class Main {
public static void main(String[] args) throws JAXBException {
String fooString = "<foo bar=\"bar\"><baz><test value=\"something\" /></baz></foo>";
JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory
.createContext(new Class<?>[] {Foo.class}, null);
javax.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler());
Foo foo = unmarshaller
.unmarshal(new StreamSource(new ByteArrayInputStream(fooString.getBytes(StandardCharsets.UTF_8))), Foo.class)
.getValue();
System.out.println(foo);
}
@XmlRootElement()
public static class Foo {
@XmlAttribute
public String bar;
@XmlElement
public Baz baz;
@Override
public String toString() {
return "<foo bar=\"" + bar + "\">" + baz.toString() + "</foo>";
}
}
@XmlRootElement
public static class Baz {
@XmlElement
public Test test;
@Override
public String toString() {
return "<baz>" + test.toString() + "</baz>";
}
}
@XmlRootElement
public static class Test {
@XmlAttribute
public String value;
@Override
public String toString() {
return "<test value=\"" + value + "\"/>";
}
}
}
这应该正确解组并打印<foo bar="bar"><baz><test value="something"/></baz></foo>。另请注意,通常您将传递 JAXBContextFactory 一个包含整个对象图的 ObjectFactory,该对象图将从您的 XML 模式生成。这可能是混乱的根源。
现在您可以将XmlAdapter 添加回来,但不要尝试将String 转换为Element,而是将Baz 转换为Element:
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.transform.stream.StreamSource;
import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
public class Main {
public static void main(String[] args) throws JAXBException {
String fooString = "<foo bar=\"bar\"><baz><test value=\"something\" /></baz></foo>";
JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory
.createContext(new Class<?>[] {Foo.class}, null);
javax.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler());
Foo foo = unmarshaller
.unmarshal(new StreamSource(new ByteArrayInputStream(fooString.getBytes(StandardCharsets.UTF_8))), Foo.class)
.getValue();
System.out.println(foo);
}
@XmlRootElement()
public static class Foo {
@XmlAttribute
public String bar;
@XmlElement
@XmlJavaTypeAdapter(ElementAdapter.class)
public Element baz;
@Override
public String toString() {
return "<foo bar=\"" + bar + "\">" + baz.toString() + "</foo>";
}
}
@XmlRootElement
public static class Baz {
@XmlElement
public Test test;
@Override
public String toString() {
return "<baz>" + test.toString() + "</baz>";
}
}
@XmlRootElement
public static class Test {
@XmlAttribute
public String value;
@Override
public String toString() {
return "<test value=\"" + value + "\"/>";
}
}
public static class ElementAdapter extends XmlAdapter<Baz, Element> {
@Override
public Element unmarshal(Baz baz) throws Exception {
StringWriter sw = new StringWriter();
// Note: it is a terrible idea to re-instantiate a context here
// Use a cached value or a singleton from before
JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory
.createContext(new Class<?>[] {Baz.class}, null);
javax.xml.bind.Marshaller m = jaxbContext.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.marshal(baz, sw);
Document document = new SAXBuilder().build(new StringReader(sw.toString()));
return document.getRootElement();
}
@Override
public Baz marshal(Element v) throws Exception {
// TODO implement this
return null;
}
}
}
这或多或少应该可以满足您的需求,尽管您可能必须在转换为 JDOM 之前清理适配器中的某些 Baz 元素(去掉 XML 前导码等)。请注意,在 ElementAdapter 中实例化另一个 JAXBContext 可能是一个糟糕的主意,并且可能有一种更有效的方法可以将 JAXB 元素转换为 JDOM 元素。这段代码很容易在调试器中单步执行,因此您可以看到发生了什么。