聲明式HTTP客戶端-Feign 使用入門詳解

什麼是 OpenFeign

OpenFeign (以下統一簡稱為 Feign) 是 Netflix 開源的聲明式 HTTP 客戶端,集成了 Ribbon 的負載均衡、輪詢演算法和 RestTemplate 的 HTTP 調用等特性,並對其進行封裝,使用者只需要在此基礎上,定義一個介面,並在介面上標註一個 FeignClient ,便可以實現 HTTP 遠程調用,上面的聲明式 HTTP 如何理解,可以理解為

​ 只需要聲明一個介面,Feign 就會通過你定義的介面,自動給你構造請求的目標地址並請求。

下面介紹下如何在項目中集成 Feign 組件,只需要遵循 SpringBoot 開發三板斧(1、加依賴,2、加註解,3、加配置)即可

環境準備

  1. 加依賴 openfeign

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
  2. 加註解 @EnableFeignClients

    @SpringBootApplication
    @EnableFeignClients
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    
  3. 加配置(由於 feign 不需要額外在 application.yml 或者 application.properties)中配置,只需要配置好調用微服務的名稱和埠即可,下面舉個例子,user-center 就是我們調用的微服務

    server:
      port: 8081
    spring:
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848
      # 微服務名稱
      application:
        name: user
    

新建 OpenFeign 介面

@FeignClient(name = "user")
public interface UserFeignClient {
    @GetMapping("/users/{id}")
    UserDto findById(@PathVariable Integer id);
}

新建 Controller 層

@Service
public class ArticleController {

    @Resource
    private UserFeignClient userFeignClient;

    public ArticleDto findByUserId(Integer userId) {
        ... ...
        ArticleDto articleDto = this.userFeignClient.findById(userId);
        ... ...
        return articleDto;

    }

}

以上,就是 openFeign 的基本使用入門了

Feign的日誌配置

由於 Feign 在沒有配置的情況下是不會列印任何日誌,如果想要看到 Feign 的日誌,需要額外的配置;但是在此之前,我們先了解下 Feign 的自定義日誌級別。

Feign 的自定義日誌級別

級別 列印內容
NONE(默認值) 不記錄任何日誌
BASIC 僅記錄請求方法、URL、響應狀態碼以及執行時間
HEADERS 記錄 BASIC 級別的基礎上,記錄請求和響應的 header
FULL 記錄請求和響應的 header、body 和元數據

Feign 的自定義日誌級別可以通過 Java 程式碼方式或配置屬性方式來實現,下面我們先介紹下程式碼的實現方式

程式碼配置方式

  1. 編寫一個 Configuration 的類

    public class UserFeignConfiguration {
        @Bean
        public Logger.Level level() {
            //	生產上不建議使用FULL,這樣會產生大量的日誌,影響性能的同時還不好定位問題。
            //	建議使用 BASIC
            return Logger.Level.FULL;
        }
    }
    
  2. 然後在對應的 Feign 介面設置 Configuration 類

    @FeignClient(name = "user", configuration = UserFeignConfiguration.class)
    public interface UserFeignClient {
        @GetMapping("/users/{id}")
        UserDto findById(@PathVariable Integer id);
    }
    
  3. 最後還要將 Feign介面類的全路徑設置在 yml 文件裡面

    logging:
      level:
    	# Feign介面類的全路徑 
    	# Feign的日誌級別是建立在Feign的介面 debug 級別之上的
        com.example.article.feignclient.UserFeignClient: debug
    

    當調用介面時,輸出日誌結果如下

    [UserFeignClient#findById] <--- HTTP/1.1 200 (794ms)
    [UserFeignClient#findById] content-type: application/json;charset=UTF-8
    [UserFeignClient#findById] date: Mon, 29 Aug 2022 10:56:27 GMT
    [UserFeignClient#findById] transfer-encoding: chunked
    [UserFeignClient#findById] 
    [UserFeignClient#findById] {"id":1,"username":"張三","createTime":"2022-08-25T17:17:04.000+0000","updateTime":"2022-08-25T17:17:04.000+0000"}
    [UserFeignClient#findById] <--- END HTTP (180-byte body)
    

    這樣,通過程式碼來設置 Feign 的日誌自定義級別方式就配置好了。但是這樣,並不是完美的解決方案,就相當於每次新建一個 Feign 介面,就要編寫一個對應的 xxxFeignConfiguration 類,然後在 Feign 介面上指定,這樣下來太繁瑣了,下面看下全局的程式碼配置方式:

    不需要在 每個 Feign 介面上面都要配置下 configuration = UserFeignConfiguration.class,我們只需要在 Application 啟動類上面,指定 @EnableFeignClients(defaultConfiguration = GlobalFeignConfiguration.class),註:這裡將 UserFeignConfiguration 已經更名為 GlobalFeignConfiguration

    @SpringBootApplication
    @EnableFeignClients(defaultConfiguration = GlobalFeignConfiguration.class)
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    

屬性配置方式

在 application.yml 加入只需要配置以下的屬性既可以實現 Feign 的自定義級別

feign:
  client:
    config:
      # 微服務名稱
      user:
        loggerLevel: full

同理,yml 文件也支援 全局的屬性配置方式

feign:
  client:
    config:
      # 全局配置(default 默認就是適用於全部微服務)
      default:
        loggerLevel: full

Feign的多參數請求構造

GET 請求

請求多參數的URL,如請求地址為 //www.xxx.me/admin/user/get?username=張三&school=陽光小學&birthDay=2012-08-01,SpringCloud 為 Feign 整合了 SpringMVC 的註解支援

  1. @SpringQueryMap 註解

    @FeignClient("user")
    public interface UserFeignClient {
      @RequestMapping(value = "/get", method = RequestMethod.GET)
      public User get(@SpringQueryMap User user);
    }
    
  2. @RequestParam註解(表單傳參)

    @FeignClient("user")
    public interface UserFeignClient {
      @RequestMapping(value = "/get", method = RequestMethod.GET)
      public User get(@RequestParam("username") String username, @RequestParam("school") String school, @RequestParam("birthDayDay") String birthDay);
    }
    
  3. @PathVariable註解(URL攜帶參數)

    @FeignClient("user")
    public interface UserFeignClient {
      @RequestMapping(value = "/get", method = RequestMethod.GET)
      public User get(@PathVariable("username") String username, @PathVariable("school") String school, @PathVariable("birthDayDay") String birthDay);
    }
    

POST 請求

Feign 默認的傳參方式就是 JSON 傳參(@RequestBody),因此定義介面的時候可以不用@RequestBody註解標註,但為了開發規範,建議加上

  1. @RequestBody 註解

    @FeignClient("user")
    public interface UserFeignClient {
      @RequestMapping(value = "/post", method = RequestMethod.POST)
      public User post(@RequestBody User user);
    }
    

超時設置

我們在通過 Feign 去調用介面,難免會遇到超時的問題,我們可以在 yml 文件設置超時屬性,防止系統拋出超時異常

feign:
    client:
        config:
		   # 全局配置(default 默認就是適用於全部微服務)
            default:
                connectTimeout: 100000
                readTimeout: 100000
            # 單獨配置
            user:
                connectTimeout: 300000
                readTimeout: 300000

Feign 性能優化

默認情況下,Feign 使用的是 UrlConnetcion 去請求,這種原生的請求方式一旦遇到高並發的情況下,響應會變得很慢,所以我們可以考慮加入連接池技術來優化性能,下面介紹下如何集成 Apache 下的 HttpClient 的連接池

  1. 加入 httpclient 依賴

    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-httpclient</artifactId>
    </dependency>
    
  2. 設置 yml 文件屬性

    feign:
    	# 這樣就設置好了 feign 請求方式是 httpclient,而不是 UrlConnetcion 
        httpclient:
            enable: true
    	    # feign的最大連接數
            max-connection: 200
     	    # feign 單個路徑的最大連接數
            max-connections-per-route: 50
    
Tags: