java - 无法为 spring Controller 编码(marshal)类型、XML 输出

标签 java spring spring-mvc jaxb spring-hateoas

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

我有一个简单的 Controller 并使用 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 hatoas jar 位于 tomcat/lib 中,Resources 类的 XML 编码不起作用,会抛出最后提到的错误。

是否可以在spring配置中将子类加载器设置为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)

我无法移动 jar ,所以需要修复 Spring 侧。顺便说一句,JSON 响应工作得很好,问题仅在于列表的 XML 响应。

最佳答案

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

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

如果您在 Controller 中创建一个直接返回 ResponseEntity 的方法,您会发现这工作正常。

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

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

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

如果我必须进一步调试这个问题,我会首先检查 Hateoas 源代码,看看他们是否有任何测试来验证 XML 序列化确实有效,如果没有测试,那么它可能会完全损坏。

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

如果我用 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));
}

关于java - 无法为 spring Controller 编码(marshal)类型、XML 输出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40481369/

相关文章:

java - 异常 MyExc3 永远不会在相应的 try 语句主体中抛出

java - 如何使用 Spring WebFlux WebFilter 结束请求并发送正确的响应?

java - 我们在spring实现微服务架构时是否创建了不同的项目

java - Spring 是从众多接口(interface)实现中单独挑选出一个接口(interface)实现吗?

java - RMI 注册表不可用错误

java - 在 1 GHz 处理器上运行 10-100 千万亿步的算法需要多长时间?

java - 我的项目层次结构中的 spring 依赖项

java - 如何构建一个同时暴露rest和soap服务的Spring Boot jar

java - Spring Hibernate 中实体字段的本地化

java - import com.fasterxml.jackson.xml 无法解析