【问题标题】:Use RestTemplate with object as data and application/x-www-form-urlencoded content type?使用带有对象作为数据和 application/x-www-form-urlencoded 内容类型的 RestTemplate?
【发布时间】:2019-06-12 14:13:18
【问题描述】:

我需要通过内容类型为application/x-www-form-urlencodedRestTemplate 发布一个对象(例如,不是MultiValueMap)。当我尝试这样做时...

HttpHeaders headers = new HttpHeaders();
HttpEntity request;

headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED)

// data is some generic type
request = new HttpEntity<>(data, headers);

// clazz is the Class<T> being returned
restTemplate.exchange(url, method, request, clazz)

...我收到以下错误:

org.springframework.web.client.RestClientException: 无法写入请求:没有为请求类型 [com.whatever.MyRequestPayload] 和内容类型 [application/x-www-form-urlencoded] 找到合适的 HttpMessageConverter

这是我在restTemplate.getMessageConverters() 中看到的:

我为什么不想提供MultiValueMap两个原因:

  1. 这是用于向多个端点发送请求的通用代码,因此专门为 x-www-form-urlencoded 添加重载只会使事情复杂化
  2. 似乎我不应该这样做——我只是不知道需要使用哪个 HttpMessageConverter 来支持将对象转换为 x-www-form-urlencoded 字符串

【问题讨论】:

    标签: java spring spring-boot


    【解决方案1】:

    我最终不得不编写一个自定义 HTTP 消息转换器,它接受任何对象并将其作为 www-form-urlencoded 内容写入请求正文:

    用法

    RestTemplate template = new RestTemplate(...);
    
    template.getMessageConverters().add(new ObjectToUrlEncodedConverter(mapper));
    

    ObjectToUrlEncodedConverter

    import com.fasterxml.jackson.annotation.JsonAnySetter;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.http.HttpInputMessage;
    import org.springframework.http.HttpOutputMessage;
    import org.springframework.http.MediaType;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.converter.HttpMessageNotReadableException;
    import org.springframework.http.converter.HttpMessageNotWritableException;
    import sun.reflect.generics.reflectiveObjects.NotImplementedException;
    
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.net.URLEncoder;
    import java.util.Collections;
    import java.util.List;
    
    public class ObjectToUrlEncodedConverter implements HttpMessageConverter
    {
        private static final String Encoding = "UTF-8";
    
        private final ObjectMapper mapper;
    
        public ObjectToUrlEncodedConverter(ObjectMapper mapper)
        {
            this.mapper = mapper;
        }
    
        @Override
        public boolean canRead(Class clazz, MediaType mediaType)
        {
            return false;
        }
    
        @Override
        public boolean canWrite(Class clazz, MediaType mediaType)
        {
            return getSupportedMediaTypes().contains(mediaType);
        }
    
        @Override
        public List<MediaType> getSupportedMediaTypes()
        {
            return Collections.singletonList(MediaType.APPLICATION_FORM_URLENCODED);
        }
    
        @Override
        public Object read(Class clazz, HttpInputMessage inputMessage) throws HttpMessageNotReadableException
        {
            throw new NotImplementedException();
        }
    
        @Override
        public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws HttpMessageNotWritableException
        {
            if (o != null)
            {
                String body = mapper
                    .convertValue(o, UrlEncodedWriter.class)
                    .toString();
    
                try
                {
                    outputMessage.getBody().write(body.getBytes(Encoding));
                }
                catch (IOException e)
                {
                    // if UTF-8 is not supporter then I give up
                }
            }
        }
    
        private static class UrlEncodedWriter
        {
            private final StringBuilder out = new StringBuilder();
    
            @JsonAnySetter
            public void write(String name, Object property) throws UnsupportedEncodingException
            {
                if (out.length() > 0)
                {
                    out.append("&");
                }
    
                out
                    .append(URLEncoder.encode(name, Encoding))
                    .append("=");
    
                if (property != null)
                {
                    out.append(URLEncoder.encode(property.toString(), Encoding));
                }
            }
    
            @Override
            public String toString()
            {
                return out.toString();
            }
        }
    }
    

    【讨论】:

    • 请问您可以为映射器对象添加示例吗?
    • @Esameldean 只需像这样创建 ObjectMapper 的一个实例: ObjectMapper mapper = new ObjectMapper(); restTemplate.getMessageConverters().add(new ObjectToUrlEncodedConverter(mapper));
    【解决方案2】:

    您是否尝试过将MappingJackson2HttpMessageConverter 添加到RestTemplate 之类的方法

    restTemplate.getMessageConverters().add(getMappingJackson2HttpMessageConverter());
    
    public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_FORM_URLENCODED));
        return mappingJackson2HttpMessageConverter;
    }
    

    【讨论】:

    • 它已经在那里了,检查截图。我会尝试为特定的媒体类型添加它。
    • 默认映射中支持的媒体Jackson2HttpMessageConverter只支持json。 Mohamed 的回答添加了另一个支持 MediaType.APPLICATION_FORM_URLENCODED 的转换器。但我怀疑 ObjectMapper 是否可以将对象转换为 x-www-form-urlencoded 主体。
    • 我觉得你得写一个类来实现HttpMessageConverter
    • 对,我这样做了,它在正文中显示为 JSON 字符串,而不是 www-form-urlencoded。
    • 另一方面,我很好奇你为什么不写一个静态的 util 方法来将 MyRequestPayload 实例转换为 MultiValueMap 实例?
    【解决方案3】:

    原因:没有转换器可以将你的java对象转换成x-www-form-urlencoded格式的请求体。

    解决方案 1:创建这种转换器,就像 @Josh M. 发布的那样。

    解决方案2:将你的java对象转换为MultiValueMap,并且spring boot中已经有一个名为FormHttpMessageConverter的转换器,它会自动将MultiValueMap转换为x-www-form-urlencoded格式的请求体。

    所以在解决方案2中,您只需将您的java对象转换为MultiValueMap

            MultiValueMap<String, String> bodyPair = new LinkedMultiValueMap();
            bodyPair.add(K1, V1);
            bodyPair.add(K1, V2);
            bodyPair.add(K2, V2);
            ...
    

    K1, V1, K2, V2, ..., 表示你的java对象中的字段名和对应的值。您在 java 类中声明的所有字段都需要添加。如果字段太多,请考虑使用 Java 反射。

    【讨论】:

      【解决方案4】:

      你可以试试https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/converter/FormHttpMessageConverter.html

      RestTemplate template = new RestTemplate(...);
      template.getMessageConverters().add(new org.springframework.http.converter.FormHttpMessageConverter.FormHttpMessageConverter());
      

      【讨论】:

        【解决方案5】:

        如果您使用的是WebMvcConfigurerAdapter,那么您可能会遇到此类错误。 WebMvcConfigurerAdapter 从 Spring 5.x.x 也被弃用。尝试使用WebMvcConfigurer。完成后,您将不得不覆盖:

        @Override
            public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}
        

        【讨论】:

          【解决方案6】:

          您可以将 FormHttpMessageConverter 用于内容类型“application/x-www-form-urlencoded”,如下所示,并在主体的 RestTemplate post 方法中将数据发送为 MultiValueMap

          <bean id="restTemplate" class="org.springframework.web.client.RestTemplate" lazy-init="true">
              <constructor-arg name="requestFactory" ref="bufferingClientHttpRequestFactory" />
              <property name="errorHandler" ref="responeErrorHandler" />
              <property name="messageConverters">
              <list>
                  <bean class="org.springframework.http.converter.FormHttpMessageConverter" />
                  <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
              </list>
          </property>
          </bean>
          

          Java 配置版本

          @Bean
          public RestTemplate restTemplate() {
              RestTemplate restTemplate = new RestTemplate();          
              restTemplate.getMessageConverters().add(new FormHttpMessageConverter());
              restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
              return restTemplate 
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-04-02
            • 2019-09-12
            • 2021-02-03
            • 1970-01-01
            • 1970-01-01
            • 2020-04-21
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多