使用Spring Cloud Feign作為HTTP客戶端呼叫遠端HTTP服務
在Spring Cloud Netflix棧中,各個微服務都是以HTTP介面的形式暴露自身服務的,因此在呼叫遠端服務時就必須使用HTTP客戶端。我們可以使用JDK原生的URLConnection
、Apache的Http Client
、Netty的非同步HTTP Client, Spring的RestTemplate
。但是,用起來最方便、最優雅的還是要屬Feign了。
Feign簡介
Feign是一種宣告式、模板化的HTTP客戶端。在Spring Cloud中使用Feign, 我們可以做到使用HTTP請求遠端服務時能與呼叫本地方法一樣的編碼體驗,開發者完全感知不到這是遠端方法,更感知不到這是個HTTP請求。比如:
@Autowired
private AdvertGropRemoteService service; // 遠端服務
public AdvertGroupVO foo(Integer groupId) {
return service.findByGroupId(groupId); // 通過HTTP呼叫遠端服務
}
- 開發者通過
service.findByGroupId()
就能完成傳送HTTP請求和解碼HTTP返回結果並封裝成物件的過程。
Feign的定義
為了讓Feign知道在呼叫方法時應該向哪個地址發請求以及請求需要帶哪些引數,我們需要定義一個介面:
@FeignClient(name = "ea") // [A] public interface AdvertGroupRemoteService { @RequestMapping(value = "/group/{groupId}", method = RequestMethod.GET) // [B] AdvertGroupVO findByGroupId(@PathVariable("groupId") Integer adGroupId) // [C] @RequestMapping(value = "/group/{groupId}", method = RequestMethod.PUT) void update(@PathVariable("groupId") Integer groupId, @RequestParam("groupName") String groupName)
- A:
@FeignClient
用於通知Feign元件對該介面進行代理(不需要編寫介面實現),使用者可直接通過@Autowired
注入。
B: @RequestMapping
表示在呼叫該方法時需要向/group/{groupId}
傳送GET
請求。
C: @PathVariable
與SpringMVC
中對應註解含義相同。
Spring Cloud應用在啟動時,Feign會掃描標有@FeignClient
註解的介面,生成代理,並註冊到Spring容器中。生成代理時Feign會為每個介面方法建立一個RequetTemplate
物件,該物件封裝了HTTP請求需要的全部資訊,請求引數名、請求方法等資訊都是在這個過程中確定的,Feign的模板化就體現在這裡。
在本例中,我們將Feign與Eureka和Ribbon組合使用,@FeignClient(name = "ea")
意為通知Feign在呼叫該介面方法時要向Eureka中查詢名為ea
的服務,從而得到服務URL。
Feign的Encoder、Decoder和ErrorDecoder
Feign將方法簽名中方法引數物件序列化為請求引數放到HTTP請求中的過程,是由編碼器(Encoder)完成的。同理,將HTTP響應資料反序列化為java物件是由解碼器(Decoder)完成的。
預設情況下,Feign會將標有@RequestParam
註解的引數轉換成字串新增到URL中,將沒有註解的引數通過Jackson轉換成json放到請求體中。注意,如果在@RequetMapping
中的method
將請求方式指定為POST
,那麼所有未標註解的引數將會被忽略,例如:
@RequestMapping(value = "/group/{groupId}", method = RequestMethod.GET)
void update(@PathVariable("groupId") Integer groupId, @RequestParam("groupName") String groupName, DataObject obj);
此時因為宣告的是GET請求沒有請求體,所以obj
引數就會被忽略。
在Spring Cloud環境下,Feign的Encoder*只會用來編碼沒有添加註解的引數*。如果你自定義了Encoder, 那麼只有在編碼obj
引數時才會呼叫你的Encoder。對於Decoder, 預設會委託給SpringMVC中的MappingJackson2HttpMessageConverter
類進行解碼。只有當狀態碼不在200 ~ 300之間時ErrorDecoder才會被呼叫。ErrorDecoder的作用是可以根據HTTP響應資訊返回一個異常,該異常可以在呼叫Feign介面的地方被捕獲到。我們目前就通過ErrorDecoder來使Feign介面丟擲業務異常以供呼叫者處理。
Feign的HTTP Client
Feign在預設情況下使用的是JDK原生的URLConnection
傳送HTTP請求,沒有連線池,但是對每個地址會保持一個長連線,即利用HTTP的persistence connection
。我們可以用Apache的HTTP Client替換Feign原始的http client, 從而獲取連線池、超時時間等與效能息息相關的控制能力。Spring Cloud從Brixtion.SR5
版本開始支援這種替換,首先在專案中宣告Apache HTTP Client和feign-httpclient
依賴:
<!-- 使用Apache HttpClient替換Feign原生httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>${feign-httpclient}</version>
</dependency>
- 然後在
application.properties
中新增:
feign.httpclient.enabled=true
總結
通過Feign, 我們能把HTTP遠端呼叫對開發者完全透明,得到與呼叫本地方法一致的編碼體驗。這一點與阿里Dubbo中暴露遠端服務的方式類似,區別在於Dubbo是基於私有二進位制協議,而Feign本質上還是個HTTP客戶端。如果是在用Spring Cloud Netflix搭建微服務,那麼Feign無疑是最佳選擇。