简体   繁体   English

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

[英]Unable to marshal type, XML output for spring controller

I am working in an environment where all the common dependent jars are residing in tomcat/lib folder, and application specific jars in war file. 我在一个环境中工作,其中所有常见的依赖jar都驻留在tomcat / lib文件夹中,而特定于应用程序的jar位于war文件中。

I have a simple controller and using spring-hateoas 我有一个简单的控制器,并使用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
}

As the spring hateoas jar is in tomcat/lib, the XML marshalling of Resources class does not work, throws error as mentioned at the end. 由于spring hateoas jar位于tomcat / lib中, Resources类的XML编组不起作用,并在最后提到了错误。

Is it possible to set the child classloader to the Jaxb converter in spring configuration so that this error can be avoided? 是否可以在弹簧配置中将子类加载器设置为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)

I won't be able to move the jars, so need the fix on the spring side. 我将无法移动广口瓶,因此需要在弹簧侧进行固定。 BTW, JSON response works just fine, issue is only with the XML response for list. 顺便说一句,JSON响应工作得很好,问题仅在于列表的XML响应。

I have reproduced the error with a small Spring boot example, so I'm pretty sure it is not a classpath issue. 我已经用一个小的Spring引导示例重现了该错误,所以我很确定这不是类路径问题。

The problem is that when the JAXBContext for the Hateoas Resources class is being constructed, there is no reference to your AccountResource class. 问题在于,在构造Hateoas资源类的JAXBContext时,没有对AccountResource类的引用。 This means that when Spring ask JAXB to serialize your ResponseEntity it breaks when it encounters AccountResource, because this class is not registered in the JAXBContext used for serialization. 这意味着当Spring要求JAXB序列化您的ResponseEntity时,它在遇到AccountResource时会中断,因为此类未在用于序列化的JAXBContext中注册。

If you make a method in you controller that returns ResponseEntity directly, you can see that this works fine. 如果您在控制器中创建一个直接返回ResponseEntity的方法,则可以看到它工作正常。

The JAXBContext is immutable, and as far as I can see there is no way to affect the construction of JAXBContext because AbstractJaxb2HttpMessageConverter.getJaxbContext() is final. JAXBContext是不可变的,据我所知,没有任何方法可以影响JAXBContext的构造,因为AbstractJaxb2HttpMessageConverter.getJaxbContext()是最终的。

I'm not an expert in JAXB, but from the documentation, it looks like Resource.getContent() is correctly annotated with @XmlAnyElement, but for some reason AccountResource is not serialized inside Resource. 我不是JAXB专家,但是从文档中看,Resource.getContent()正确地用@XmlAnyElement进行了注释,但是由于某些原因,AccountResource没有在Resource中序列化。

If my analysis is correct this would be a problem for everyone using Hateoas with XML, so either no one is doing that, or I'm wrong. 如果我的分析是正确的,那么对于每个使用Hateoas和XML的人来说都是个问题,所以要么没人在做,要么我做错了。 Do you really need it to produce XML ? 您真的需要它来生成XML吗?

If I had to debug this any further I would start by checking out the Hateoas source, and see if they have any test that verifies that XML serialization actually works, if there is no test there is a chance it is completely broken. 如果必须进一步调试它,我将首先检查Hateoas源,看看他们是否有任何测试可以验证XML序列化确实有效,如果没有测试,则它有可能被完全破坏。

Edit If you can live without the namespaces* I believe I have found a solution. 编辑如果您可以没有命名空间*,我相信我已经找到了解决方案。

If I replace the default Jaxb2RootElementHttpMessageConverter with MappingJackson2XmlHttpMessageConverter, and use @JacksonXmlRootElement, I can get the following output (*It may be possible to add the namespaces using MixIns, but I have not checked). 如果我将默认的Jaxb2RootElementHttpMessageConverter替换为MappingJackson2XmlHttpMessageConverter,并使用@JacksonXmlRootElement,我将获得以下输出(*可以使用MixIns添加名称空间,但我尚未检查)。

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

In order to modify the HttpMessageConverters after construction you need Spring 4.1.3 or newer, and make you configuration extend WebMvcConfigurationSupport, this allows you to do the following: 为了在构造后修改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));
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM