RestTemplate为什么不支持泛型对象输出?如何逆天改命?
- 需求背景
- 传参时的解决方案
2.1 Jackson传入“子类”对象的class对象
-2.1.1 效果图
-2.1.2 改进方案
2.2 FastJson传入“子类”对象的class对象 - 源码层次的解决方案
- 思考
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.png2.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 + "]");
}
-
GenericHttpMessageConverter是消息转换器的子类,它可以支持传入Type对象,来支持泛型的反序列化。常用的FastJson、Jackson、Gson消息转换器均实现的是这个接口。
-
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>
对象的泛型。