反射機制是Java的一個非常實用的特性. 基于反射, 我們可以實現(xiàn)下面的接口
<T> T foo(..., Class<T> tClass);
將類型作為參數(shù)傳入方法中, 方法可以根據(jù)具體的類實現(xiàn)不同的邏輯, 返回不同數(shù)據(jù)類型的結(jié)果. 這十分有利于減少代碼的冗余度和耦合度, 在復雜多樣的業(yè)務場景中非常有用.
然而, 當反射遇到泛型, 問題就變的棘手起來. 我們常常會遇到這么一個情景: 類型參數(shù)是繼承了某個泛型類的子類的參數(shù), 我們需要根據(jù)泛型基類和子類參數(shù), 構(gòu)造出子類類型. 與List<String>, Map<Integer, String> 等子類不一樣, 這里的子類類型參數(shù)是動態(tài)指定的.
舉個例子, 在開發(fā)中, 我們經(jīng)常需要接入一些外部系統(tǒng)的Restful API. 通常, 除了業(yè)務數(shù)據(jù)部分外, 這些API的請求構(gòu)建, 響應處理的邏輯都是一樣的. 自然而然, 我們會想把接口的請求邏輯封裝為一個方法來實現(xiàn)復用. 然而, 在處理響應的過程中, 我們需要對接口的返回結(jié)果進行反序列化. 假設(shè)外部接口返回的數(shù)據(jù)結(jié)構(gòu)如下
@Data
public class ResponseDTO<T> {
private String code;
private String message;
private T data;
}
T 為業(yè)務數(shù)據(jù)類型, 不同的接口業(yè)務數(shù)據(jù)結(jié)構(gòu)會不一樣. 我們需要根據(jù) ResponseDTO 和 T, 反射出類型 ResponseDTO<T> . 如果實現(xiàn)不了泛型類的參數(shù)化, 對于每一個業(yè)務接口的數(shù)據(jù)類型T, 我們都要顯式繼承 ResponseDTO 得到靜態(tài)的子類類型, 未免特別冗余和不優(yōu)雅.
幸好, 至 Java 1.7 開始, java.lang.reflect 提供了Interface ParameterizedType. 通過實現(xiàn)這個接口, 我們可以實現(xiàn)泛型類的類型的參數(shù)化, 代碼如下:
public class ParameterizedTypeImpl implements ParameterizedType {
private final Class raw;
private final Type[] args;
private final Type owner;
public ParameterizedTypeImpl(Class raw, Type[] args, Type owner) {
this.raw = raw;
this.args = args != null ? args : new Type[0];
this.owner = owner;
}
@Override
public Type[] getActualTypeArguments() {
return args;
}
@Override
public Type getRawType() {
return raw;
}
@Override
public Type getOwnerType() {
return owner;
}
}
利用這個類, 我們就可以實現(xiàn)參數(shù)化類型, 封裝出某外部系統(tǒng)通用的HTTP調(diào)用方法:
GET請求
private <T> T myHttpGet(HttpGet httpGet, Type dataType) {
HttpClient httpClient = HttpClients.createDefault();
try {
HttpResponse response = httpClient.execute(httpGet);
if (response.getStatusLine().getStatusCode()== HttpStatus.SC_OK) {
String result = EntityUtils.toString(response.getEntity());
Type parameterizedTypeClass = // 構(gòu)造響應類型 ResponseDTO<T>
new ParameterizedTypeImpl(ResponseDTO.class, new Type[]{dataType}, ResponseDTO.class);
ResponseDTO<T> responseBody = gson.fromJson(result, parameterizedTypeClass); //使用GSON反序列化響應
if (responseBody.getCode() != 0) {
throw new MyException(
String.format("[MY.HttpGet]請求失敗, url=%s, details=%s",
httpGet.getURI(), gson.toJson(responseBody.getMessage())),
MyErrorCode.ES_REQUEST_FAIL);
}
return responseBody.getContent();
}
throw new MyException("[MY.HttpGet]" + response.getStatusLine().toString(), MyErrorCode.ES_REQUEST_FAIL);
} catch (Exception ex) {
throw new MyException("[MY.HttpGet]" + ex.getMessage(), MyErrorCode.ES_REQUEST_FAIL);
}
}
POST請求
private <T> T myHttpPost(HttpPost httpPost, Type dataType) {
HttpClient httpClient = HttpClients.createDefault();
try {
HttpResponse response = httpClient.execute(httpPost);
if (response.getStatusLine().getStatusCode()== HttpStatus.SC_OK){
String result= EntityUtils.toString(response.getEntity());
Type responseTypeClass = // 構(gòu)造響應類型 ResponseDTO<T>
new ParameterizedTypeImp(ResponseDTO.class, new Type[]{dataType}, ResponseDTO.class);
ResponseDTO<T> responseBody = gson.fromJson(result, responseTypeClass); // 使用GSON反序列化響應
if(responseBody.getCode()!=0) {
throw new MyException(
String.format("[MY.HttpPost]請求失敗, url=%s, details=%s",
httpPost.getURI(), gson.toJson(responseBody.getMessage())),
MyErrorCode.ES_REQUEST_FAIL);
}
return responseBody.getContent();
}
throw new MyException("[MY.HttpPost]" + response.getStatusLine().toString(), MyErrorCode.ES_REQUEST_FAIL);
} catch (Exception ex) {
ex.printStackTrace();
throw new MyException("[MY.HttpPost]" + ex.getMessage(), MyErrorCode.ES_REQUEST_FAIL);
}
}
在調(diào)用時, 我們構(gòu)造好請求對象和業(yè)務部分數(shù)據(jù)結(jié)構(gòu), 就可以得到對應的返回.
HttpPost httpPost = new HttpPost(host + "/api/dataA");
httpPost.addHeader("Content-Type", "application/json; charset=utf-8");
//...
StringEntity se = new StringEntity(gson.toJson(reqBody), "utf-8");
httpPost.setEntity(se);
try {
DataA resbody = myHttpPost(httpPost, DataA.class);
} catch (Exception ex) {
throw new MyException(
"調(diào)用接口失敗, " + ex.getMessage(),ErrorCode.ES_REQUEST_FAIL);
}
甚至, 泛型類的子類也可以參數(shù)化傳入:
List<DataB> resbodyB = myHttpPost(httpPost, new TypeToken<List<DataB>>(){}.getType()); // 這里用到Gson的TypeToken
子類的參數(shù)類型也可以參數(shù)化, 使用ParameterizedTypeImp構(gòu)造其類型即可:
Type typeDataCP = new ParameterizedTypeImp(DataC.class, new Type[]{tClass}, DataC.class); // Class<P> tClass
DataC<P> resbodyC = myHttpPost(httpPost, typeDataCP);