RestTemplate 使用问题记录

  |   0 评论   |   77 浏览

背景

项目中之前调用外部http接口一直使用的是httpclient,现在项目重构使用了springboot框架,而且spring-web自带RestTemplate,所以直接用该类发出http请求,不再依赖httpclient了。

RestTemplate 配置

/**
 * 注入 RestTemplate
 * @param factory
  * @return
  */
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory){
       return new RestTemplate();
}

测试代码

public static void main(String[] args) {
  RestTemplate restTemplate = new RestTemplate();
  ResponseEntity<Dto> dto = restTemplate.getForEntity("url", Dto.class);
  System.out.println(dto);
}

遇到的问题

在调用一个外部接口时,提示

Exception in thread "main" org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class cn.test.dto.Dto] and content type [text/json;charset=UTF-8]

异常提示很明显,没有找到合适的HttpMessageConverter来处理Content-type为[text/json;charset=UTF-8]格式的返回值。

注意提示:****content type [text/json;charset=UTF-8]

然而我们看注入RestTemplate的代码new RestTemplate();,直接调用构造方法,我们看源码如下:

/**
 * Create a new instance of the {@link RestTemplate} using default settings.
 * Default {@link HttpMessageConverter}s are initialized.
 */
 public RestTemplate() {
   this.messageConverters.add(new ByteArrayHttpMessageConverter());
  this.messageConverters.add(new StringHttpMessageConverter());
  this.messageConverters.add(new ResourceHttpMessageConverter());
  this.messageConverters.add(new SourceHttpMessageConverter());
  this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());

  if (romePresent) {
      this.messageConverters.add(new AtomFeedHttpMessageConverter());
  this.messageConverters.add(new RssChannelHttpMessageConverter());
  }

   if (jackson2XmlPresent) {
      this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
  }
   else if (jaxb2Present) {
      this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
  }

   if (jackson2Present) {
      this.messageConverters.add(new MappingJackson2HttpMessageConverter());
  }
   else if (gsonPresent) {
      this.messageConverters.add(new GsonHttpMessageConverter());
  }
}

可以清楚的看出,构造方法已经加入了很多类型的HttpMessageConverter,如最常用StringHttpMessageConverter和处理json的MappingJackson2HttpMessageConverter,那么为什么返回值是json却报异常呢?

我们看this.messageConverters.add(new MappingJackson2HttpMessageConverter());这行代码的实现,也很简单调用无参构造方法,如下:

/**
 * Construct a new {@link MappingJackson2HttpMessageConverter} using default configuration
 * provided by {@link Jackson2ObjectMapperBuilder}.
 */
 public MappingJackson2HttpMessageConverter() {
   this(Jackson2ObjectMapperBuilder.json().build());
}

/**
 * Construct a new {@link MappingJackson2HttpMessageConverter} with a custom {@link ObjectMapper}.
 * You can use {@link Jackson2ObjectMapperBuilder} to build it easily.
 * @see Jackson2ObjectMapperBuilder#json()
 */
 public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
   super(objectMapper, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
}

可以看到最后会调用父类的构造方法,同时传入两个支持的MediaType(MediaType.APPLICATION_JSON和“application/*+json”):

protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper, MediaType... supportedMediaTypes) {
   super(supportedMediaTypes);
  init(objectMapper);
}

其中MediaType.APPLICATION_JSON为“application/json”。

继续看restTemplate.getForEntity()方法,该方法最终会doExecute方法:

protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback,
  ResponseExtractor<T> responseExtractor) throws RestClientException {

   Assert.notNull(url, "'url' must not be null");
  Assert.notNull(method, "'method' must not be null");
  ClientHttpResponse response = null;
  try {
      ClientHttpRequest request = createRequest(url, method);
  if (requestCallback != null) {
         requestCallback.doWithRequest(request);
  }
      response = request.execute();
  handleResponse(url, method, response);
  if (responseExtractor != null) {//处理返回值
         return responseExtractor.extractData(response);
  }
      else {
         return null;
  }
   }
   catch (IOException ex) {
      String resource = url.toString();
  String query = url.getRawQuery();
  resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
  throw new ResourceAccessException("I/O error on " + method.name() +
            " request for \"" + resource + "\": " + ex.getMessage(), ex);
  }
   finally {
      if (response != null) {
         response.close();
  }
   }
}

请求完成后,会调用return responseExtractor.extractData(response);方法处理返回值,该方法的实现在HttpMessageConverterExtractor类,如下:

public T extractData(ClientHttpResponse response) throws IOException {
   MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
  if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
      return null;
  }
   MediaType contentType = getContentType(responseWrapper);

  for (HttpMessageConverter messageConverter : this.messageConverters) {
      if (messageConverter instanceof GenericHttpMessageConverter) {
         GenericHttpMessageConverter genericMessageConverter =
               (GenericHttpMessageConverter) messageConverter;
			   //下面是重点:判断注册的HttpMessageConverter是否能支持Response的contentType
  if (genericMessageConverter.canRead(this.responseType, null, contentType)) {
            if (logger.isDebugEnabled()) {
               logger.debug("Reading [" + this.responseType + "] as \"" +
                     contentType + "\" using [" + messageConverter + "]");
  }
            return (T) genericMessageConverter.read(this.responseType, null, responseWrapper);
  }
      }
      if (this.responseClass != null) {
         if (messageConverter.canRead(this.responseClass, contentType)) {
            if (logger.isDebugEnabled()) {
               logger.debug("Reading [" + this.responseClass.getName() + "] as \"" +
                     contentType + "\" using [" + messageConverter + "]");
  }
            return (T) messageConverter.read((Class) this.responseClass, responseWrapper);
  }
      }
   }

   throw new RestClientException("Could not extract response: no suitable HttpMessageConverter found " +
         "for response type [" + this.responseType + "] and content type [" + contentType + "]");
}

看到这里已经很清楚了,RestTemplate默认的json处理器MappingJackson2HttpMessageConverter只能处理Content-type为“application/json”或“application/*+json”的内容,而在genericMessageConverter.canRead(this.responseType, null, contentType)判断中,会找不到text/json对应的处理类,就会抛出异常了。

好了,问题的原因找出来的,那么该怎么解决呢?

很容易想到,我们可以扩展MappingJackson2HttpMessageConverter类来实现:

public class TextJson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {

    public TextJson2HttpMessageConverter() {
	  super();
	  List mediaTypes = new ArrayList<>();
	  mediaTypes.add(new MediaType("text", "json"));
	  setSupportedMediaTypes(mediaTypes);
	}
}

评论

发表评论

validate