Feign 遠端呼叫

📢 本文由 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 底層用戶端實作:

  • URLConnection:預設實作,不支援連線池

  • Apache HttpClient :支援連線池

  • OKHttp:支援連線池

使用連線池可以提升 Feign 的效能

以下範例為取代為 HttpClient

  1. 引用相依性
1
2
3
4
5
<!-- httpClient 的相依性 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>
  1. 設定連線池
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 無法使用,有兩種方式解決

  1. 指定 Feign 應該掃描的套件
1
@EnableFeignClients(basePackages = "net.yexca.feign.clients")
  1. 指定要載入的 Client 介面
1
@EnableFeignClients(clients = {UserClient.class})