我正在使用Jaxb2Marshaller通过spring @ResponseBody注释来编组Java bean。对于JSON编组工作正常。但对于xml,我不断得到HTTP 406响应。 Jaxb2Marshaller类中的一点点挖掘显示它为有界类检查@XmlRootElement(参见下面的代码片段)。
从xsd生成java代码时,我的pojo不包含@XmlRootElement,并且AnnotationMethodHandlerAdapter未识别正确的消息转换器,最终导致406。
我没有从xsd自动生成java代码,而是创建了自己的pojo类并使用了@XmlRootElement。编组工作正常。
我想理解为什么让@XmlRootElement检查有界类很重要。或者在xsd中将元素指定为@XmlRootElement的任何方式。
Jaxb2Marshaller的代码片段:
public boolean supports(Class clazz) {
return supportsInternal(clazz, true);
}
private boolean supportsInternal(Class<?> clazz, boolean checkForXmlRootElement) {
if (checkForXmlRootElement && clazz.getAnnotation(XmlRootElement.class) == null) {
return false;
}
if (clazz.getAnnotation(XmlType.class) == null) {
return false;
}
if (StringUtils.hasLength(getContextPath())) {
String className = ClassUtils.getQualifiedName(clazz);
int lastDotIndex = className.lastIndexOf('.');
if (lastDotIndex == -1) {
return false;
}
String packageName = className.substring(0, lastDotIndex);
String[] contextPaths = StringUtils.tokenizeToStringArray(getContextPath(), ":");
for (String contextPath : contextPaths) {
if (contextPath.equals(packageName)) {
return true;
}
}
return false;
}
else if (!ObjectUtils.isEmpty(classesToBeBound)) {
return Arrays.asList(classesToBeBound).contains(clazz);
}
return false;
}
编辑: Blaise的回答帮我解决了@XmlRootElement问题。但是,如果有人知道为什么需要检查XmlRootElement,那么这将是一个很好的信息。
答案 0 :(得分:5)
为什么要检查@XmlRootElement注释
Spring在将对象编组为XML时需要一个根元素。 JAXB提供了两种机制来执行此操作:
由于对象未包装在JAXBElement中,Spring确保满足其他条件。
如何生成@XmlRootElement
JAXB将为XML模式中的所有全局元素生成@XmlRootElement注释。以下内容将导致@XmlElement:
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="foo">
<xsd:complexType>
...
</xsd:complextType>
</xsd:element>
</xsd:schema>
@XmlRootElement未生成时
不会为全局类型生成@XmlRootElement注释。
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="foo" type="foo"/>
<xsd:complexType name="foo">
...
</xsd:complexType>
</xsd:schema>
而是以@XmlElementDecl注释的形式在ObjectFactory类(使用@XmlRegistry注释)中捕获与全局类型关联的全局元素。这些注释
package generated;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;
@XmlRegistry
public class ObjectFactory {
private final static QName _Foo_QNAME = new QName("", "foo");
public Foo createFoo() {
return new Foo();
}
@XmlElementDecl(namespace = "", name = "foo")
public JAXBElement<Foo> createFoo(Foo value) {
return new JAXBElement<Foo>(_Foo_QNAME, Foo.class, null, value);
}
}
@XmlElementDecl注释提供与@XmlRootElement类似的信息,可用于解组操作。但是JAX-RS实现可能不会利用@XmlElementDecl,因为编组操作需要将对象包装在JAXBElement对象中以提供根元素名称/命名空间。
答案 1 :(得分:2)
这是一个已知问题: https://jira.springsource.org/browse/SPR-7931
“在Jaxb2Marshaller中检查@XmlRootElement注释应该是可选的”
答案 2 :(得分:1)
您可以将JaxbElement用于没有@XmlRootElement批注的类。如果从xsd生成代码,则@XmlRootElement注释仅放置在未引用的顶级对象
Edit
See @Blaise Doughan answer.
@XmlRootElement will be placed only if there is no reference to that type in another type.