spring framework方案面试

RestTemplate为什么不支持泛型对象输出?如何逆天改命?

2021-07-28  本文已影响0人  小胖学编程
  1. 需求背景
  2. 传参时的解决方案
    2.1 Jackson传入“子类”对象的class对象
    -2.1.1 效果图
    -2.1.2 改进方案
    2.2 FastJson传入“子类”对象的class对象
  3. 源码层次的解决方案
  4. 思考

1. 需求背景

jackson反序列化得到的对象无法强转为泛型对象。

在远程调用时,某些接口的格式是相似的,例如:

@Data
public class ResultObject<T> {

    private int status;

    private String message;

    private T data;
}

例如上面这种格式,data存储的时实际的响应值

但是我们在使用RestTemplate调用第三方接口时:

ResponseEntity<ResultObject> responseEntity =
                restTemplate.postForEntity(url, requestEntity, ResultObject.class);

只能如上进行调用,responseType无法传递泛型类型。

SpringBoot的HttpMessageConverter使用(1)RestTemplate中的应用
由该文章可知,RestTemplate默认的消息转换器为Jackson。

问题:远程调用得到的responseEntity中的data域是LinkedHashMap类型,无法强转为泛型对象!!!

2. 传参时的解决方案

2.1 Jackson传入“子类”对象的class对象

2.1.1 效果图

在方法中使用匿名内部类的方式是不可行的。

代码模拟:

image.png

那么采用子类携带泛型对象的方式...

image.png

原因:jackson源码中显示:支持静态类,但是不支持方法内部的类。

image.png

2.1.2 改进方案

将匿名内部类替换为静态内部类,查看效果,是支持泛型对象的输出的。

image.png

2.2 FastJson传入“子类”对象的class对象

Jackson传入匿名内部类的子类对象出现异常,只是个例。FastJson就不会出现异常。

传入非泛型对象.png 传入的是匿名内部类.png

当然,FastJson由于bug原因,不推荐作为消息转换器来替换Jackson。

3. 源码层次的解决方案

消息的最终反序列化是:org.springframework.web.client.HttpMessageConverterExtractor#extractData处进行的。

@SuppressWarnings({"unchecked", "rawtypes", "resource"})
public T extractData(ClientHttpResponse response) throws IOException {
   MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
   if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
      return null;
   }
   MediaType contentType = getContentType(responseWrapper);

   try {
      for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
        //Jackson会走这个分支
         if (messageConverter instanceof GenericHttpMessageConverter) {
            GenericHttpMessageConverter<?> genericMessageConverter =
                  (GenericHttpMessageConverter<?>) messageConverter;
            if (genericMessageConverter.canRead(this.responseType, null, contentType)) {
               if (logger.isDebugEnabled()) {
                  ResolvableType resolvableType = ResolvableType.forType(this.responseType);
                  logger.debug("Reading to [" + resolvableType + "]");
               }
              //注意,此处的responseType是class对象。
               return (T) genericMessageConverter.read(this.responseType, null, responseWrapper);
            }
         }
         if (this.responseClass != null) {
            if (messageConverter.canRead(this.responseClass, contentType)) {
               if (logger.isDebugEnabled()) {
                  String className = this.responseClass.getName();
                  logger.debug("Reading to [" + className + "] as \"" + contentType + "\"");
               }
               return (T) messageConverter.read((Class) this.responseClass, responseWrapper);
            }
         }
      }
   }
   catch (IOException | HttpMessageNotReadableException ex) {
      throw new RestClientException("Error while extracting response for type [" +
            this.responseType + "] and content type [" + contentType + "]", ex);
   }

   throw new RestClientException("Could not extract response: no suitable HttpMessageConverter found " +
         "for response type [" + this.responseType + "] and content type [" + contentType + "]");
}
  1. GenericHttpMessageConverter是消息转换器的子类,它可以支持传入Type对象,来支持泛型的反序列化。常用的FastJson、Jackson、Gson消息转换器均实现的是这个接口。

  2. Class对象是Type对象的子类,此处的Type对象(responseType)实际上是Class对象。

核心思路:只要responseType传入的是Type对象,那么是不是便可以支持泛型?

继承RestTemplate类,实现个性化子类:

public class MyRestTemplate extends RestTemplate {
    public MyRestTemplate() {
    }

    public MyRestTemplate(ClientHttpRequestFactory requestFactory) {
        super(requestFactory);
    }

    public MyRestTemplate(List<HttpMessageConverter<?>> messageConverters) {
        super(messageConverters);
    }

    /**
     * 传入的是Type对象
     */
    public <T> ResponseEntity<T> postForEntity(String url,
                                               @Nullable Object request,
                                               Type type,
                                               Object... uriVariables) throws RestClientException {

        //其他均是源码,未进行改动
        RequestCallback requestCallback = httpEntityCallback(request, type);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(type);
        return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
    }
}

调用上述方法实际传入的是

Type type = new TypeReference<ResultObject<User>>() {}.getType();

这样做,便可以使得RestTemplate支持泛型对象。

4. 思考

为什么RestTemplate设计者不提供可传入Type对象的postForEntity方法?

虽然通过子类可实现传入Type对象,但是无法传入泛型<T>。而传入Class<T>对象便可以规定其ResponseEntity<T>对象的泛型。

上一篇下一篇

猜你喜欢

热点阅读