為什么不用Spring RestTemplate 與 Feign
Feign
Feign是局域網(wǎng)調(diào)用袱蜡,而這里需求則是跨平臺的廣域網(wǎng)調(diào)用楷扬。
RestTemplate - 先提出兩個需求:
- 我們需要像Feign那樣配置一個,聲明式的接口 ,在易用性敬锐、可讀性等方面能有較好的表現(xiàn)闰非。
- 請求時蒲祈,BaseUrl也就是域名地址是動態(tài)變化的。
=>回答: 對RestTemplate來說萝嘁,每次請求前我們需要提前配置好網(wǎng)關(guān)地址梆掸,然后再加上請求的Path。無法實(shí)現(xiàn)動態(tài)的BaseUrl改變與請求接口的統(tǒng)一管理牙言。
Retrofit能解決這些痛點(diǎn)嗎酸钦?
- Retrofit支持BaseUrl的動態(tài)改變。
- 它能將請求接口規(guī)劃在每個Retrofit Client中進(jìn)行統(tǒng)一管理咱枉。
大致背景
需要完成的是B端類似應(yīng)用市場的功能卑硫,存在Pass與SaaS端,Pass端主要是各個用戶對平臺自己進(jìn)行部署安裝蚕断。SaaS則是我們維護(hù)在公網(wǎng)上的Pass
欢伏,用戶可以通過它們自己的Pass平臺訪問到SaaS從而拿到應(yīng)用數(shù)據(jù)實(shí)現(xiàn)下載,上傳亿乳,查看等硝拧,所以這里的需求就是需要在Pass端對遠(yuǎn)程SaaS進(jìn)行跨平臺的調(diào)用径筏。
在項(xiàng)目中使用retrofit的演進(jìn)過程
- 通常如果存在一個baseurl,只需要配置并build一個retrofit障陶,然后createClient滋恬,創(chuàng)建出訪問接口的Client。
- 但在這里我們的baseUrl會時刻改變抱究,意味著SaaS的地址會發(fā)送變化恢氯。
- 而且有不同Client訪問不同平臺的微服務(wù),比如Pass端的訪問接口包含了對多個微服務(wù)的請求鼓寺,那么它們就應(yīng)該劃分為不同的Client勋拟,統(tǒng)一管理。
存在多個baseurl
- 創(chuàng)建一個工廠類:使用靜態(tài)字段BaseUrl和Client侄刽,每次請求去查驗(yàn)當(dāng)前Client是否為空指黎,url是否改變,如果改變或?yàn)榭談t使用build一個Retrofit創(chuàng)建新的Client州丹。
- 為什么不考慮使用官方推薦的動態(tài)url來做醋安,是因?yàn)槊看握埱蟮臅r候都需要跟上自己的全路徑,而這里我們希望將Path能放在一個統(tǒng)一接口的各個具體的調(diào)用方法上墓毒。
private static String baseUrl;
private static AppMarketClient marketClient;
public static AppMarketClient createClient(String newUrl) {
if (marketClient == null || !baseUrl.equals(newUrl)) {
baseUrl = newUrl;
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
marketClient = retrofit.create(AppMarketClient.class);
}
return marketClient;
}
存在多個BaseUrl,同時 存在多個Client
使用工廠類吓揪,類中使用Map,BaseUrl作key所计,Client作value緩存Client柠辞,痛點(diǎn)在于我每個url都要跟上請求的具體微服務(wù)網(wǎng)關(guān)Path,這樣才能區(qū)分相同BaseUrl不同微服務(wù)的Client主胧,比如http://xxx.com/devops/xxx叭首,與http://xxx.com/market/xxx。
private static final Map<String, RetrofitClient> retrofitClients = new HashMap<>();
public static RetrofitClient createClient(String baseUrl, Class<? extends RetrofitClient> clientClass) {
//如果baseUrl被修改或map中不存在這個RetrofitClient踪栋,則創(chuàng)建
if (!retrofitClients.containsKey(baseUrl)) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
retrofitClients.put(baseUrl, retrofit.create(clientClass));
}
return retrofitClients.get(baseUrl);
}
在Github上看到Retrofit廢除BaseUrl的Issue焙格,其中提到使用攔截器進(jìn)行統(tǒng)一處理,從而動態(tài)改變BaseUrl
private static final Retrofit retrofit;
// 默認(rèn)值
private static String baseUrl = "http://localhost";
private static final Map<String, Object> retrofitClients = new HashMap<>();
static {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(chain -> {
HttpUrl httpUrl = HttpUrl.parse(baseUrl);
if (ObjectUtils.isEmpty(httpUrl)) {
throw new CommonException("error.retrofit.baseUrl.illegal", baseUrl);
}
Request request = chain.request();
Request.Builder builder = request.newBuilder();
HttpUrl oldHttpUrl = request.url();
HttpUrl newFullUrl = oldHttpUrl
.newBuilder()
.host(httpUrl.host())
.build();
return chain.proceed(builder.url(newFullUrl).build());
})
.build();
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
}
public static Object createClient(String baseUrl, Class clientClass) {
RetrofitClientFactory.baseUrl = baseUrl;
String className = clientClass.getSimpleName();
if (!retrofitClients.containsKey(className)) {
retrofitClients.put(className, retrofit.create(clientClass));
}
return retrofitClients.get(className);
}
添加到spring管理
// 原有工廠類
private ApplicationContext applicationContext;
private RetrofitConfig retrofitConfig;
public RetrofitClientFactory(RetrofitConfig retrofitConfig) {
this.retrofitConfig = retrofitConfig;
}
@Override
public void setApplicationContext(@NonNull ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public Object getRetrofitBean(String baseUrl, Class clientName) {
retrofitConfig.setBaseUrl(baseUrl);
return applicationContext.getBean(clientName.getSimpleName());
}
// Spring配置文件
private String baseUrl = "http://localhost";
// todo 修改全局的baseUrl時的線程安全 (修改baseUrl夷都,獲取RetrofitClient眷唉,發(fā)出請求,這三步是一個原子操作)
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
@Bean("RemoteTokenRetrofitClient")
public RemoteTokenRetrofitClient remoteTokenRetrofitClient() {
Retrofit retrofit = getCommonRetrofit();
return retrofit.create(RemoteTokenRetrofitClient.class);
}
@Bean("AppMarketRetrofitClient")
public AppMarketRetrofitClient appMarketRetrofitClient() {
Retrofit retrofit = getCommonRetrofit();
return retrofit.create(AppMarketRetrofitClient.class);
}
private Retrofit getCommonRetrofit() {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(chain -> {
HttpUrl httpUrl = HttpUrl.parse(baseUrl);
if (ObjectUtils.isEmpty(httpUrl)) {
throw new CommonException("error.retrofit.baseUrl.illegal", baseUrl);
}
Request request = chain.request();
Request.Builder builder = request.newBuilder();
HttpUrl oldHttpUrl = request.url();
HttpUrl newFullUrl = oldHttpUrl
.newBuilder()
.host(httpUrl.host())
.port(httpUrl.port())
.build();
return chain.proceed(builder.url(newFullUrl).build());
})
.build();
return new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
}