探索过程,
以前是用 apache 阿帕奇的 HttpClient
然后同事提到了 Spring 的RestTemplate 比较方便
后来查这方面的资料也是确实如此,看名字(xxTemplate)也知道使用了模板设计模式,简化了重复的代码的编写,不用每次都将体转换成流再发到请求execute方法,也不用每次都在finally里面 close 所有资源
了解了RestTemplate的使用,查看了源码,发现里面的所有方法最后都是执行execute,针对简单的rest请求可以用封装好的template方法(get/post/put/deleteForEntity/Object)
对于复杂的请求(需要自定义请求头,返回类型带泛型)最好还是自行重新用exchange方法封装一次
例如这样:
这里省略了请求异常的处理(其实是自己也还没研究怎么自定义4开头和5开头的状态返回的处理方法。。。哈哈)先暂时用Spring默认的异常处理
public <T> T get(String url, Class<T> respClass, Map<String, Object> urlParams){
return restTemplate.exchange(
url,
HttpMethod.GET,
null, // HttpEntity 如果用默认头可以置空,自定义头可以 new HttpEntity(new HttpHeaders())
respClass,
urlParams == null ? new HashMap() : urlParams
)
}
public <T> T post(String url, Class<T>,HttpEntity httpEntity respClass){
return restTemplate.exchange(
url,
HttpMethod.POST,
httpEntity ,
respClass
)
}
但是发现对接的接口的返回类型都是同样的结构 code msg data
就想到用带类型参数的类 (泛型类) 来统一处理请求的响应体,
Bean:BaseResp
public class BaseResp<T> {
private int code;
private String msg;
private T data;
//省略 get set 方法。。。
}
但是如果在调用自己封装方法,直接传入 BaseResp.class 就会报错
LinkedHashMap can not cast to BaseResp
因为我传入的class没有指定对应的<T> (不可以BaseResp<RespBody>.class,编译器报错),对于带有泛型的data属性 exchange 方法无法解析将转换成什么类型,就会转换成默认的LinkedHashMap了
查了网络资源
然后看exchange的实现方法 (这里安利一下idea的ctrl+p)
发现处了传class 还可以传type
网络上查了很多资料,调试了很多次。。。
有这个方法
尝试1:
new ParameterizedTypeReference<T>(){};
但是T还是T,却不是我实例化的RespBody
也试过用反射(又是一番学习,还好年前的这段时间没有什么任务可以随意划水)
反射出传入的respClass的Type类型
public <T> T getHttpResp2(String url, Class<T> clazz){
Method thisMethod = null;
Method[] methods = this.getClass().getMethods();
for (Method method : methods) {
if(method.getName().equals("getHttpResp2")){
thisMethod = method;
break;
}
}
Type[] genericParameterTypes = thisMethod.getGenericParameterTypes(); // String url,Class<T> clazz
Type[] actualTypeArguments = ((ParameterizedType) genericParameterTypes[1]).getActualTypeArguments(); // T
Type ttype = actualTypeArguments[0]; // T.class
ParameterizedTypeReference<T> objectParameterizedTypeReference = ParameterizedTypeReference.forType(ttype);
...
...
}
然后用
尝试2:
ParameterizedTypeReference.forType(ttype);
还是不行
然后看到Class类是Type类的实现(网上有人直接cast (Class)Type)
所以尝试直接传class到forType里面(这时候发现之前的反射真是饶了一大圈,也算是重新学习了泛型的反射吧,class好像是不能直接穿换成type)
有用过这样的方法
尝试3:
ParameterizedTypeReference.forType(respClass);
但是返回的typeReference还是能反射到我实例化的泛型的类型
上面三种尝试都是不能解析成我想要的RespBody
都是报错 LinkedHashMap can not cast to
最后突然想起看到过exchange的源码,里面也是要一个具体的Type才可以解释
exchange的源码
就想到我的方法是不是过度封装了,最后换成这样子
最终方法实现:
public <T> T post(String url, @NotNull HttpEntity requestEntity, ParameterizedTypeReference<T> parameterizedTypeReference){
ResponseEntity<T> responseEntity = restTemplate.exchange(
url,
HttpMethod.POST,
requestEntity,
parameterizedTypeReference
);
return responseEntity.getBody();
}
让调用者先指定好了type
方法调用:
ParameterizedTypeReference<TestModelType<List<TestModelBody>>> parameterizedTypeReference =
new ParameterizedTypeReference<TestModelType<List<TestModelBody>>>() {};
TestModelType<List<TestModelBody>> listTestModelType =
restTemplateUtils.get(testUrl, parameterizedTypeReference);
再传入我行封装好的方法。
估计即使在实例化的时候指定了泛型的类型,java底层虚拟机也是不能反射出的吧。不然Spring也不用提供ParameterizedTypeReference这个方法了。
同样阿里的json工具也是有个这个方法
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
String jsonString = jsonStringEntity.getBody();
T body = JSONObject.parseObject(jsonString, clazz); // 直接传Class不能确定泛型
JSONObject.parseObject(
jsonString,
new TypeReference<TestModelType<List<TestModelBody>>>(){}
);
第一次写博客,年前这个星期真实划水划到没力了。
放假前来一编划水一周的总结
总结:
对有泛型类需求的处理
不能过度封装直接传入一个class
需要用ParameterizedTypeReference来指定具体的泛型
后续:
// TODO
年后再研究一下怎么处理自定义请求异常
参考:
你真的了解Java泛型参数? : https://blog.csdn.net/u012881904/article/details/80813294
Spring RestTemplate详解 : https://www.cnblogs.com/caolei1108/p/6169950.html
T和Class以及Class的理解 : https://blog.csdn.net/witewater/article/details/53462385
RestTemplate中使用ParameterizedTypeReference参数化类型支持泛型,主要是List https://www.jianshu.com/p/886922facc5f