【问题标题】:No suitable HttpMessageConverter found for request type JAXBElement没有为请求类型 JAXBElement 找到合适的 HttpMessageConverter
【发布时间】:2017-08-03 07:54:04
【问题描述】:

我将一个 JAXB 对象发布到 REST 服务。生成的类没有 XMLRootElement,因此我使用对象工厂 createXMl 方法创建它。当我手动添加 XMLRootElement 时,它可以工作,但这只是一种解决方法,因为 JAXB 类总是在没有 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)

以下 HttpMessageConverters 已经注册:

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

【问题讨论】:

    标签: xml spring jaxb spring-integration spring-rest


    【解决方案1】:

    该问题与 MarshallingHttpMessageConverter 类中的 canWritecanRead 方法有关。这些方法检查编组器和解组器是否支持一个类。就我而言,启用 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);
    

    【讨论】:

      【解决方案2】:

      如果您需要使用RestTemplate 发布一个没有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. 确保您通过的任何元素都可以由您指定的 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(""));
                      }
              };
      
      }
      

      【讨论】:

        【解决方案3】:

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

        <?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>
        

        【讨论】:

          【解决方案4】:

          嗯,确实它不适合你,因为:

          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 包装器的解决方案。

          【讨论】:

          • 感谢 Artem .. 我可以通过为我的 xsd 添加一个 .xjb 文件来解决此问题,该文件将 XMLRootElement 添加到我生成的 java 类中.. xml 现在解析成功。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-07-26
          • 2015-04-29
          • 2014-03-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多