看到標題大家可能會有點疑惑吧:OpenFeign 不是挺好用的嗎?尤其是微服務之間的遠程調用,平時用的也挺習慣的,為啥要替換呢? ...
目錄
前言
看到標題大家可能會有點疑惑吧:OpenFeign 不是挺好用的嗎?尤其是微服務之間的遠程調用,平時用的也挺習慣的,為啥要替換呢?
背景和原因是這樣的:
- 部門/團隊在安全性上有所考慮,即儘可能地減少/消除引入外部依賴,儘量只使用自研依賴、apache、Spring等必須的開源依賴;
- 而 OpenFeign 的使用則是引入了 Spring Cloud 依賴(不在安全要求範圍內),所以需要考慮替換;
- 為以後團隊的項目上 Spring 6做鋪墊,Spring 6 會有 Spring 內置的 Http Interface 發起遠程服務調用。
下麵將從介紹 OpenFeign、常見的 Http API 以及重點介紹 Spring 自帶的 RestTemplate Http 模板這3個方面展開。
一、何為OpenFeign
OpenFeign 是 Spring Cloud 在 Feign 的基礎上支持了 SpringMVC 的註解,如 @RequesMapping 等,其底層預設使用的是 URLConnection 實現。
OpenFeign 的 @FeignClient 註解可以解析 SpringMVC 的@RequestMapping 註解下的介面,並通過動態代理的方式產生實現類,實現類中做負載均衡並調用其他服務。
1.1@FeignClient註解
只要是使用 OpenFeign 那麼這個註解是一定會使用到的,該註解的主要屬性如下:
- url:可以手動指定 @FeignClient 調用的遠程服務地址,如果同時聲明 url 和 name 則以 url 為準,此時 name 僅作為該 FeignClient 的名稱而已;
- name:指定當前 FeignClient 的名稱,如果項目使用了 Ribbon,那麼 name 屬性會作為微服務的名稱,用於服務的發現;
- value:實際上和 name 是用一個屬性,因為這兩個屬性互相使用了別名,使用的時候兩者選其一即可;
- path:定義當前 FeignClient 的統一首碼,即表示所有調用的遠程服務都會走這個 path 聲明的 http 首碼;
- configuration:Feign 的配置類,可以自定義 Feign 的 Encoder、Decoder、LogLevel、Contract 等;
- fallback:定義容錯的處理類,當調用遠程介面失敗或超時,會調用對應介面的容錯邏輯,fallback 指定的類必須實現 @FeignClient 標記的介面。
簡單示例如下:
@FeignClient(url = "https://xxx.abcdef.com", name = "SubmitTaskClient",
configuration = OpenFeignFormConfig.class, fallback = HystrixFallbackConfig.class)
public interface SubmitTaskClient {
/**
* 調用遠程介面實現,入參為 json 字元串
* @param paramJsonStr
* @param header
* @return
*/
@PostMapping
String submitNormalTask(@RequestBody String paramJsonStr, @RequestHeader Map<String, String> header);
/**
* 調用遠程介面實現,入參為 map 的表單形式
* @param map
* @return
*/
@PostMapping(value = "/task/create", headers = {"content-type=application/x-www-form-urlencoded"})
String submitTransTask(Map<String, ?> map);
}
1.2註意事項
在遠程服務調用一般存在兩種情況:
-
遠程服務在註冊中心
如果遠程服務的提供方已經註冊到註冊中心,那麼 name 或者 value 的值為:註冊中心的服務名稱,且必須為所有客戶端指定一個 name 或者 value。
@FeignClient(name = "SubmitTaskService", configuration = OpenFeignFormConfig.class, fallback = HystrixFallbackConfig.class)
-
單獨的遠程 http 介面
此處 name 的值為當前 feignClient 客戶端的名稱,指定的 url 則為遠程服務的地址。
@FeignClient(url = "https://xxx.abcdef.com", name = "SubmitTaskClient", configuration = OpenFeignFormConfig.class)
以上兩種方式都能正常進行遠程服務調用。name 可以為註冊中心的服務名稱,同時有 url 屬性時,name 就與註冊中心服務名稱無關。
二、常見的Http API
OpenFeign 本質上還是使用 http 請求完成服務的調用,其實使用以下的這些 Http API 經過適當的改造後,也可以達到效果。
2.1Apache
在後端領域,出現比較早而且使用仍然很廣泛的 HTTP 客戶端框架非 Apache HttpClien 莫屬了,目前大量項目和公司仍在採用該框架。
Apache HttpClient 有著不錯的性能、豐富的功能以及強大的自定義實現等特色。但是隨著技術的發展和設計理念的改變,Apache HttpClient 顯的有些落伍了。
個人認為其最不受歡迎的點主要在於 API 的設計過於臃腫,大量的配置需要手動聲明,當見過了更多好的的 Http API 後你可能就會不太想繼續用了。當然公司框架正在使用 Apache HttpClient 的情況下也無可厚非,雖然複雜點,但用還是可以用的。
引入 pom 依賴:
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
POST 請求示例如下:
public String apacheHttpClientPost(String url, String params) throws Exception {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-Type", "application/json");
String charSet = "UTF-8";
StringEntity entity = new StringEntity(params, charSet);
httpPost.setEntity(entity);
CloseableHttpResponse response = null;
try {
response = httpclient.execute(httpPost);
StatusLine status = response.getStatusLine();
int state = status.getStatusCode();
if (state == 200) {
HttpEntity responseEntity = response.getEntity();
return EntityUtils.toString(responseEntity);
}
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
2.2Okhttp
OkHttp3 是 OkHttp 發展到版本3.0之後的名字。在 maven 中央倉庫搜索 okhttp,可以看到 3.0 之後的版本統一稱為 OkHttp3。
OKHttp3 是一個當前主流的網路請求的開源框架,由 Square 公司開發,目標是用於替代 HttpUrlConnection 和 Apache HttpClient。
<!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.1</version>
</dependency>
POST 請求示例如下:
public String okHttpPostMethod(String url,String body, OkHttpClient okHttpClient) throws IOException {
MediaType JSON_TYPE = MediaType.parse("application/json");
Request request = new Request.Builder()
.url(url).post(RequestBody.create(JSON_TYPE, body)).addHeader("Content-Type", "application/json")
.build();
Response response = null;
try {
response = okHttpClient.newCall(request).execute();
} catch (Exception e) {
e.printStackTrace();
}
assert response != null;
if (response.isSuccessful()) {
return response.body() == null ? "" : response.body().string();
}
return null;
}
對於需要單獨處理 POST、GET 等請求的情況來說,OkHttp3 是很適合的。
但是對於一些通用請求,比如在一個通用方法的參數里只需要傳入 Method 枚舉(POST、GET 等)就可以實現對應類型的請求,Hutool 和 RestTemplate 可能更為合適。
2.3Hutool
Hutool 中的工具方法來自每個用戶的精雕細琢,它涵蓋了 Java 開發底層代碼中的方方面面,是國內 Java 開發工具類庫的集大成者,很多公司的很多項目都在用。
其中 Hutool 的 http 部分是基於 HttpUrlConnection 的 Http 客戶端封裝,大致發起調用的步驟:首先構建一個http請求,包括請求的地址、請求方式、請求頭、請求參數等信息,然後執行請求返回一個 http 響應類,最後通過這個相應類可以獲取響應的主體、是否請求成功等信息。
但遺憾的是,團隊里也有比較明確的安全規定:不允許在項目中引入 Hutool 依賴包。
引入 pom 依賴:
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.8</version>
</dependency>
創建通用請求的示例如下:
public String huToolMethod(String url, HttpMethod httpMethod, RequestBody body) {
Map<String, String> headers = new HashMap<>();
headers.put(HttpHeaders.CONTENT_TYPE, "application/json;charset=utf-8");
// 創建通用請求, 可以涵蓋所有常見的 HTTP 方法, 同時放入 url
HttpRequest request = HttpUtil.createRequest(Method.valueOf(httpMethod.name()), url);
// 放入請求的 header 和 body
HttpResponse response = request.addHeaders(headers).body(JSON.toJSONString(body)).execute();
return response.body();
}
三、RestTemplate
RestTemplate 是 Spring 框架用來訪問 RESTFUL 服務的客戶端模板類,主要功能有:
1、發起 HTTP 請求,包括 RESTful 風格的 GET,POST,PUT,DELETE 等常見方法;
2、自動將響應結果映射為 Java 對象,不用手動解析 JSON 或 XML。
3、自定義設置請求頭、消息轉碼、Cookie 等功能。
4、對不同的輸入/輸出類型提供對應的方法,如字元串、對象、多部分等。
5、同時還支持遠程調用,不受同源策略限制。
引入 pom 依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
配置類:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory){
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(10000);
factory.setReadTimeout(10000);
return factory;
}
}
3.1詳解.execute()
.execute() 是 RestTemplate 中最常見的關於執行 HTTP 請求的方法,它允許開發人員高度定製 HTTP 請求。
先來給一段示例:
public String restTemplateExecuteMethod(String url, String token, Object body, HttpMethodName method){
HttpHeaders httpHeaders = new HttpHeaders();
// headers:HttpHeaders 類型,包括所有頭信息
httpHeaders.add("Authorization", token);
httpHeaders.add("Content-Type", "application/json;charset=utf-8");
// body:請求體,可以是任何對象,也可以是 null
HttpEntity<Object> httpEntity = new HttpEntity<>(JSON.toJSONString(body), httpHeaders);
RequestCallback requestCallback = restTemplate.httpEntityCallback(httpEntity, Object.class);
ResponseExtractor<ResponseEntity<Object>> responseExtractor = restTemplate.responseEntityExtractor(Object.class);
// 發送請求,method.name() 表示傳入的方法,包括 GET、POST、DELETE 等
ResponseEntity<Object> entity = restTemplate.execute(url, HttpMethod.valueOf(method.name()), requestCallback, responseExtractor);
// 直接返回 body
Assert.notNull(entity, "返回體為空!");
log.info("---返回的內容:{}---", JSON.toJSONString(entity.getBody()));
return JSON.toJSONString(entity.getBody());
}
下麵是一些對象的介紹:
-
HttpEntity 對象
它主要有兩個作用:
1、表示 HTTP 請求:當表示 HTTP 請求時,HttpEntity 有兩個主要組成部分:請求頭和請求體。
2、表示 HTTP 響應:當表示 HTTP 響應時,有三個部分:狀態碼、響應頭和響應體。
其中的參數:
- headers:HttpHeaders 類型,包括所有頭信息;
- body:請求或響應體,可以是任何對象,也可以是null;
- statusCode:HttpStatus 類型,只有在表示響應時才有效。
-
RequestCallback 對象
RequestCallback 是 RestTemplate中用來定製HTTP請求的一個介面,可以設置請求頭、請求體、查詢字元串參數。
Callback介面只有一個方法:
void doWithRequest(ClientHttpRequest request) throws IOException
四、文章小結
文章的最後,我選擇了 okhttp3 和 RestTemplate 來進行 OpenFeign 的替換工作:okhttp3 處理單個 POST/GET 等請求,使用.execute() 處理通用 HTTP 請求。
那麼如何使用 Http API 代替 OpenFeign 進行遠程服務調用的分享到這裡就結束了,如有不足和錯誤,還請大家指正。或者你有其它想說的,也歡迎大家在評論區交流!