无法编组类型,XML 控制器的输出 spring

Unable to marshal type, XML output for spring controller

我在一个环境中工作,其中所有常见的依赖 jar 都驻留在 tomcat/lib 文件夹中,而应用程序特定的 jar 在 war 文件中。

我有一个简单的控制器并使用 spring-hateoas

@RestController
@ExposesResourceFor(AccountResource.class)
@RequestMapping("/accounts")
public class AccountController {

   @RequestMapping(method = { RequestMethod.GET })
   public ResponseEntity<Resources<AccountResource>> getAccounts() {
       List<Account> accounts = //get list of accounts;
       return new ResponseEntity<Resources<AccountResource>>(
            this.accountResourceAssembler.toEmbeddedList(accounts),
            HttpStatus.OK);
   }
}


@XmlRootElement(name = "account")
@Relation(value = "account", collectionRelation = "accounts")
public class AccountResource extends ResourceWithEmbeddeds {
  private Account account;

    //getters
}

由于 spring hateoas jar 在 tomcat/lib 中,Resources class 的 XML 编组不起作用,如结尾。

是否可以在 spring 配置中将子 classloader 设置为 Jaxb 转换器,从而避免此错误?

com.sun.istack.internal.SAXException2: unable to marshal type "package.AccountResource" as an element because it is not known to this context.
    com.sun.xml.internal.bind.v2.runtime.XMLSerializer.reportError(XMLSerializer.java:234)
    com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeRoot(ClassBeanInfoImpl.java:323)
    com.sun.xml.internal.bind.v2.runtime.property.ArrayReferenceNodeProperty.serializeListBody(ArrayReferenceNodeProperty.java:103)
    com.sun.xml.internal.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:144)
    com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:345)
    com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsSoleContent(XMLSerializer.java:578)
    com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeRoot(ClassBeanInfoImpl.java:326)
    com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:479)
    com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:308)
    com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:236)
    org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter.writeToResult(Jaxb2RootElementHttpMessageConverter.java:187)
    org.springframework.http.converter.xml.AbstractXmlHttpMessageConverter.writeInternal(AbstractXmlHttpMessageConverter.java:66)
    org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:195)
    org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:239)
    org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:183)
    org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:81)
    org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:126)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:832)
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:743)
    org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:961)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:895)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
    org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:858)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:687)

我无法移动罐子,因此需要 spring 方面的修复。顺便说一句,JSON 响应工作正常,问题仅在于列表的 XML 响应。

我已经用一个小 Spring 引导示例重现了错误,所以我很确定这不是 class 路径问题。

问题在于,在构造 Hateoas 资源 class 的 JAXBContext 时,没有对您的 AccountResource class 的引用。这意味着当 Spring 要求 JAXB 序列化您的 ResponseEntity 时,它会在遇到 AccountResource 时中断,因为此 class 未在用于序列化的 JAXBContext 中注册。

如果您在控制器中创建一个直接 returns ResponseEntity 的方法,您会发现它工作正常。

JAXBContext 是不可变的,据我所知,没有办法影响 JAXBContext 的构造,因为 AbstractJaxb2HttpMessageConverter.getJaxbContext() 是最终的。

我不是 JAXB 方面的专家,但从文档来看,Resource.getContent() 似乎使用 @XmlAnyElement 进行了正确注释,但由于某些原因,AccountResource 未在 Resource 中序列化。

如果我的分析是正确的,这对每个使用 Hateoas 和 XML 的人来说都是一个问题,所以要么没有人这样做,要么我错了。你真的需要它来产生 XML 吗?

如果我不得不进一步调试它,我会首先检查 Hateoas 源代码,看看他们是否有任何测试来验证 XML 序列化确实有效,如果没有测试,则有一个有可能完全坏了。

编辑 如果您可以在没有命名空间的情况下生活*,我相信我已经找到了解决方案。

如果我用 MappingJackson2XmlHttpMessageConverter 替换默认的 Jaxb2RootElementHttpMessageConverter,并使用@JacksonXmlRootElement,我可以获得以下输出(*可能可以使用 MixIns 添加命名空间,但我没有检查过)。

<Resources xmlns="">
<links></links>
<content>
    <content>
        <account>
            ....
        </account>
        <links></links>
    </content>
</content>

为了在构造后修改 HttpMessageConverters,您需要 Spring 4.1.3 或更新版本,并使您的配置扩展 WebMvcConfigurationSupport,这允许您执行以下操作:

@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    for (Iterator<HttpMessageConverter<?>> iterator = converters.iterator(); iterator.hasNext(); ) {
        HttpMessageConverter<?> converter = iterator.next();
        if (converter instanceof Jaxb2RootElementHttpMessageConverter) {
            iterator.remove();
        }
    }

    ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.xml().applicationContext(this.getApplicationContext()).build();
    converters.add(new MappingJackson2XmlHttpMessageConverter(objectMapper));
}