📢 本文由 gemini-2.5-flash 翻譯
Feign 是一個宣告式的 HTTP 用戶端,Github:
https://github.com/OpenFeign/feign
簡單用法
引用相依性
1
2
3
4
| <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
|
在啟動類別加入註解以開啟 Feign 的功能
1
2
| @EnableFeignClients
@SpringBootApplication
|
撰寫 Feign 用戶端
1
2
3
4
5
| @FeignClient("userService")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
|
取代 RestTemplate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| @Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
// @Autowired
// private RestTemplate restTemplate;
@Autowired
private UserClient userClient;
public Order queryOrderById(Long orderId) {
// 1.查詢訂單
Order order = orderMapper.findById(orderId);
// 2.查詢使用者
// String url = "http://localhost:8081/user/" + order.getUserId();
// String url = "http://userService/user/" + order.getUserId();
// User user = restTemplate.getForObject(url, User.class);
// Feign
User user = userClient.findById(order.getUserId());
// 3.封裝使用者資訊
order.setUser(user);
// 4.回傳
return order;
}
}
|
自訂設定
Feign 支援自訂設定來覆寫預設設定,可修改的設定如下:
| 類型 | 作用 | 說明 |
|---|
| feign.Logger.Level | 修改日誌等級 | 包含四種不同的等級:NONE、BASIC、HEADERS、FULL |
| feign.codec.Decoder | 回應結果的解析器 | HTTP 遠端呼叫的結果解析,例如將 JSON 字串解析為 Java 物件 |
| feign.codec.Encoder | 請求參數編碼 | 將請求參數編碼,以便透過 HTTP 請求傳送 |
| feign. Contract | 支援的註解格式 | 預設是 SpringMVC 的註解 |
| feign. Retryer | 失敗重試機制 | 請求失敗的重試機制,預設沒有,但會使用 Ribbon 的重試 |
通常只需設定日誌等級,日誌等級有:
- NONE:不記錄任何日誌資訊,這是預設值
- BASIC:僅記錄請求的方法、URL、回應狀態碼以及執行時間
- HEADERS:在 BASIC 的基礎上,額外記錄了請求和回應的標頭資訊
- FULL:記錄所有請求和回應的明細,包括標頭資訊、請求本體、中繼資料
設定方式有兩種,一種是設定檔,另一種是建立 Bean
設定檔
設定檔針對單一服務
1
2
3
4
5
| feign:
client:
config:
userservice: # 針對某個微服務的設定
loggerLevel: FULL # 日誌等級
|
針對所有服務
1
2
3
4
5
| feign:
client:
config:
default: # 這裡使用 default 代表全域設定,如果寫服務名稱,則是針對某個微服務的設定
loggerLevel: FULL # 日誌等級
|
bean
先宣告一個類別,然後宣告一個 Logger.Level 的物件
1
2
3
4
5
6
| public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC; // 日誌等級為 BASIC
}
}
|
如果要在全域生效,請放在啟動類別的 @EnableFeignClients 註解中
1
| @EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class)
|
如果是區域生效,請放在 @FeignClient 註解中
1
| @FeignClient(value = "userService", configuration = DefaultFeignConfiguration .class)
|
Feign 使用優化
Feign 底層用戶端實作:
使用連線池可以提升 Feign 的效能
以下範例為取代為 HttpClient
- 引用相依性
1
2
3
4
5
| <!-- httpClient 的相依性 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
|
- 設定連線池
1
2
3
4
5
6
7
8
9
| feign:
client:
config:
default: # default 全域設定
loggerLevel: BASIC # 日誌等級,BASIC 代表基本的請求和回應資訊
httpclient:
enabled: true # 開啟 Feign 對 HttpClient 的支援
max-connections: 200 # 最大連線數
max-connections-per-route: 50 # 每個路徑的最大連線數
|
最佳實踐
所謂最佳實踐,就是在使用過程中歸納的經驗,是一種最佳的使用方式
Feign 的用戶端與服務提供者的 Controller 程式碼非常相似
1
2
3
4
5
6
7
8
9
10
11
12
| // Feign 用戶端
@FeignClient("userService")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
// Controller
@GetMapping("/user/{id}")
public User queryById(@PathVariable("id") Long id) {
return userService.queryById(id);
}
|
對於重複的程式碼,有兩種簡化方法
方式一:繼承
為消費者的 FeignClient 和提供者的 Controller 定義統一的父介面作為標準
定義一個介面
1
2
3
4
| public interface UserAPI{
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
|
Feign 用戶端實作
1
2
| @FeignClient(value="userService")
public interface UserClient extends UserAPI{}
|
Controller 類別
1
2
3
4
5
6
| @RestController
public class UserController implements UserAPI{
public User findById(@PathVariable("id") Long id){
// 程式碼
}
}
|
優點:簡單,實作了程式碼共享
缺點:
- 服務提供者、消費者強耦合
- 參數列表中的註解映射並不會繼承,因此 Controller 中必須再次宣告方法、參數列表、註解
方式二:抽離方式
將 FeignClient 抽離為獨立模組,並且把介面相關的 POJO、預設的 Feign 設定都放到這個模組中,提供給所有消費者使用
建立一個新專案,引用 Feign 起始相依性
1
2
3
4
| <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
|
在消費者端引用 feign-api 的相依性
當定義的 FeignClient 不在 SpringBootApplication 的掃描套件範圍時,這些 FeignClient 無法使用,有兩種方式解決
- 指定 Feign 應該掃描的套件
1
| @EnableFeignClients(basePackages = "net.yexca.feign.clients")
|
- 指定要載入的 Client 介面
1
| @EnableFeignClients(clients = {UserClient.class})
|