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

时间:2016-11-08 07:36:07

标签: java spring spring-mvc jaxb spring-hateoas

我正在一个环境中工作,所有常见的从属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类的XML编组不起作用,抛出最后提到的错误。

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

我无法移动罐子,所以需要弹簧侧的固定装置。顺便说一下,JSON响应工作得很好,问题只出在列表的XML响应上。

1 个答案:

答案 0 :(得分:3)

我用一个小的Spring启动示例重现了错误,所以我很确定它不是类路径问题。

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

如果你在控制器中创建一个直接返回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));
}