找不到适合请求类型 JAXBElement 的 HttpMessageConverter

No suitable HttpMessageConverter found for request type JAXBElement

我正在将 JAXB 对象发布到 REST 服务。生成的 class 没有 XMLRootElement,因此我使用 Object Factory createXMl 方法创建它。当我手动添加 XMLRootElement 时它起作用了,但这只是一种解决方法,因为 JAXB classes 总是在没有 XMLRootElement 的情况下生成。发布请求时编组的 XMl 似乎有一些问题。

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_XML);
    String userAndPass = "Test:Test123";
    headers.add("Authorization", "Basic " + Base64Utility.encode(userAndPass.getBytes()));

    JAXBElement<DocumentDef> documentDef = PrintFactory.createPrintObjects();

    HttpEntity<JAXBElement<DocumentDef>> request = new HttpEntity<>(documentDef, headers);

    MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
    map.add("lang", "2");

    ResponseEntity<String> result = restTemplate.exchange(url, HttpMethod.POST, request, String.class, map);

PrintFactory.java

      public JAXBElement<DocumentDef> createPrintObjects() {

       DocumentDef documentDef = new DocumentDef();
       JAXBElement<DocumentDef> documentDefJAXBElement = factory.createXml(documentDef);
       return documentDefJAXBElement;
       }

ObjectFactory.java

        /**
 * Create an instance of {@link JAXBElement }{@code <}{@link DocumentDef }{@code >}}
 * 
 */
@XmlElementDecl(namespace = "http://www.example.com/testservice", name = "xml")
public JAXBElement<DocumentDef> createXml(DocumentDef value) {
    return new JAXBElement<DocumentDef>(_Xml_QNAME, DocumentDef.class, null, value);
}

错误:

org.springframework.web.client.RestClientException: Could not write request: no suitable HttpMessageConverter found for request type [javax.xml.bind.JAXBElement] and content type [application/xml]
at org.springframework.web.client.RestTemplate$HttpEntityRequestCallback.doWithRequest(RestTemplate.java:859)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:617)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:588)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:507)

以下 HttpMessageConverter 已注册:

org.springframework.http.converter.ByteArrayHttpMessageConverter@68022358, org.springframework.http.converter.StringHttpMessageConverter@7b3a8b9f, org.springframework.http.converter.StringHttpMessageConverter@645e9bc0, org.springframework.http.converter.ResourceHttpMessageConverter@7f438dba, org.springframework.http.converter.xml.SourceHttpMessageConverter@2c0def9c, org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter@46ee015c, org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@2c833e50, org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@339b6365, org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter@1e9a965b

嗯,确实它对你不起作用,因为:

JAXBElement<DocumentDef> documentDef = PrintFactory.createPrintObjects();
HttpEntity<JAXBElement<DocumentDef>> request = new HttpEntity<>(documentDef, headers);

同时Jaxb2RootElementHttpMessageConverter是为了这个:

* <p>This converter can read classes annotated with {@link XmlRootElement} and
* {@link XmlType}, and write classes annotated with {@link XmlRootElement},
* or subclasses thereof.

public boolean canWrite(Class<?> clazz, MediaType mediaType) {
    return (AnnotationUtils.findAnnotation(clazz, XmlRootElement.class) != null && canWrite(mediaType));
}

当你的JAXBElement完全没有@XmlRootElement时。

尝试找出没有 JAXBElement 包装器的解决方案。

我已经通过创建一个 .xjb 文件解决了这个问题,该文件在生成时自动将 XMLRootElement 注释附加到父 java class,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<jxb:bindings xmlns:jxb="http://java.sun.com/xml/ns/jaxb" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
          jxb:extensionBindingPrefixes="xjc" version="1.0">
<jxb:bindings schemaLocation="mySchema.xsd" node="/xs:schema">
    <jxb:globalBindings>
        <xjc:simple/>
    </jxb:globalBindings>
</jxb:bindings>

如果您需要 post 一个没有 XMLRootElement 注释的 jaxb 对象 ,一个解决方案是使用自定义 HttpMessageConverter :

// jaxb context specific to your xml elements
JAXBContext jaxbContext =
    JAXBContext.newInstance(ObjectFactory.class.getPackage().getName());

// custom converter (see declaration below)
JaxbElementHttpMessageConverter converter = new JaxbElementHttpMessageConverter();
converter.setJaxbContext(jaxbContext);

RestTemplate template = new RestTemplate();
template.getMessageConverters().add(converter); // <--- here

// your object needs to be wrapped around a JAXBElement
YourRequestObject yourObject = ...;
JAXBElement<YourRequestObject> element =
    new ObjectFactory().createYourRequestObject(request);

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);

ResponseEntity<YourResponseObject> response = 
    template.postForEntity("/your/url", 
                           HttpEntity<>(element, headers), 
                           YourResponseObject.class, 
                           params);

请注意,此转换器会尝试将任何对象转换为 JAXBElement,因此您需要:

  1. 确保您通过它的任何元素都可以 marshalled/unmarshalled 您指定的 jaxb 上下文;
  2. 将其添加到消息转换器列表的末尾,以便首先尝试更具体的消息转换器。

JaxbElementHttpMessageConverter定义如下:

/**
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter} that can read and write XML using JAXB2.
*
* <p>This converter can read and write classes not annotated with {@link XmlRootElement} as long as they are wrapped into a {@link JAXBElement} and that a specific Jaxb context is provided.
*/
public class JaxbElementHttpMessageConverter extends AbstractJaxb2HttpMessageConverter<Object> {

        private JAXBContext jaxbContext;
        private EntityResolver entityResolver = NO_OP_ENTITY_RESOLVER;

        public void setJaxbContext(JAXBContext jaxbContext) {
                this.jaxbContext = jaxbContext;
        }

        public void setEntityResolver(EntityResolver entityResolver) {
                this.entityResolver = entityResolver;
        }

        @Override
        public boolean canRead(Class<?> clazz, MediaType mediaType) {
                return clazz.isAssignableFrom(JAXBElement.class) && canRead(mediaType);
        }

        @Override
        public boolean canWrite(Class<?> clazz, MediaType mediaType) {
                return clazz.isAssignableFrom(JAXBElement.class) && canWrite(mediaType);
        }

        @Override
        protected boolean supports(Class<?> clazz) {
                // should not be called, since we override canRead/Write
                throw new UnsupportedOperationException();
        }

        @Override
        protected Object readFromSource(Class<?> clazz, HttpHeaders headers, Source source) {
                try {
                        source = processSource(source);
                        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
                        JAXBElement<?> jaxbElement = unmarshaller.unmarshal(source, clazz);
                        return jaxbElement.getValue();
                }
                catch (UnmarshalException ex) {
                        throw new HttpMessageNotReadableException("Could not unmarshal to [" + clazz + "]: " + ex.getMessage(), ex);

                }
                catch (JAXBException ex) {
                        throw new HttpMessageConversionException("Could not instantiate JAXBContext: " + ex.getMessage(), ex);
                }
        }

        protected Source processSource(Source source) {
                if (source instanceof StreamSource) {
                        StreamSource streamSource = (StreamSource) source;
                        InputSource inputSource = new InputSource(streamSource.getInputStream());
                        try {
                                XMLReader xmlReader = XMLReaderFactory.createXMLReader();
                                xmlReader.setEntityResolver(entityResolver);
                                return new SAXSource(xmlReader, inputSource);
                        }
                        catch (SAXException ex) {
                                logger.warn("Processing of external entities could not be disabled", ex);
                                return source;
                        }
                }
                else {
                        return source;
                }
        }

        @Override
        protected void writeToResult(Object o, HttpHeaders headers, Result result) {
                try {
                        Marshaller marshaller = jaxbContext.createMarshaller();
                        setCharset(headers.getContentType(), marshaller);
                        marshaller.marshal(o, result);
                }
                catch (MarshalException ex) {
                        throw new HttpMessageNotWritableException("Could not marshal [" + o + "]: " + ex.getMessage(), ex);
                }
                catch (JAXBException ex) {
                        throw new HttpMessageConversionException("Could not instantiate JAXBContext: " + ex.getMessage(), ex);
                }
        }

        private void setCharset(MediaType contentType, Marshaller marshaller) throws PropertyException {
                if (contentType != null && contentType.getCharset() != null) {
                        marshaller.setProperty(Marshaller.JAXB_ENCODING, contentType.getCharset().name());
                }
        }

        private static final EntityResolver NO_OP_ENTITY_RESOLVER = new EntityResolver() {
                @Override
                public InputSource resolveEntity(String publicId, String systemId) {
                        return new InputSource(new StringReader(""));
                }
        };

}

此问题与 MarshallingHttpMessageConverter class 中的 canWritecanRead 方法有关。这些方法检查编组器和解组器是否支持 class。就我而言,启用 jaxb 元素支持很有帮助。


MarshallingHttpMessageConverter converter = new   MarshallingHttpMessageConverter();
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setSupportJaxbElementClass(true);
marshaller.setContextPath(ObjectFactory.class.getPackage().getName());
converter.setMarshaller(marshaller);

RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(converter);