Feign的使用

我願成為你頭頂的雲發表於2020-12-31

前言

總感覺遠端呼叫微服務使用Ribbon + RestTemplate這樣的組合有點麻煩,因為要先註冊RestTemplate的Bean到Spring中,然後使用它提供的getForEntity或其他方法來發出HTTP請求獲取,感覺這樣有點不太好用,那麼有沒有其他更簡便好用的方法來實現微服務的遠端呼叫呢?Netflix公司提供了更好的封裝,那就是Feign。

1、Feign介紹

Feign是Netflix公司開源的輕量級rest客戶端,使用Feign可以非常方便的實現Http 客戶端。Feign是一種負載均衡的HTTP客戶端, 使用Feign呼叫API就像呼叫本地方法一樣,從避免了呼叫目標微服務時,需要不斷的解析/封裝json 資料的繁瑣。Feign整合了Ribbon。Ribbon+eureka是面向微服務程式設計,而Feign是面向介面程式設計。

Fegin是一個宣告似的web服務客戶端,它使得編寫web服務客戶端變得更加容易。使用Fegin建立一個介面並對它進行註解。它具有可插拔的註解支援包括Feign註解與JAX-RS註解,Feign還支援可插拔的編碼器與解碼器,Spring Cloud 增加了對 Spring MVC的註解,Spring Web 預設使用了HttpMessageConverters, Spring Cloud 整合 Ribbon 和 Eureka 提供的負載均衡的HTTP客戶端 Feign。

2、Feign的優點

Feign旨在使編寫Java Http客戶端變得更容易。在使用Ribbon+RestTemplate時,利用RestTemplate對http請求的封裝處理,形成了一套模版化的呼叫方法。但是在實際開發中,由於對服務依賴的呼叫可能不止一處,往往一個介面會被多處呼叫,所以通常都會針對每個微服務自行封裝一些客戶端類來包裝這些依賴服務的呼叫。所以,Feign在此基礎上做了進一步封裝,由他來幫助我們定義和實現依賴服務介面的定義。在Feign的實現下,我們只需建立一個介面並使用註解的方式來配置它(以前是Dao介面上面標註Mapper註解,現在是一個微服務介面上面標註一個Feign註解即可),即可完成對服務提供方的介面繫結,簡化了使用Spring cloud Ribbon時,自動封裝服務呼叫客戶端的開發量。

Feign整合了Ribbon。利用Ribbon維護了MicroServiceCloud-Dept的服務列表資訊,並且通過輪循實現了客戶端的負載均衡。而與Ribbon不同的是,通過feign只需要定義服務繫結介面且以宣告式的方法,優雅而簡單的實現了服務呼叫。

總結:Feign是對Ribbon的進一步封裝,目的是讓遠端呼叫更加簡便。

3、Feign的使用

新建一個maven工程,test-feign。

(1) pom依賴
我是直接複製別的工程,很多依賴用不上:

	<dependencies>

		<!-- spring-data-jpa依賴 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
			<version>2.3.4.RELEASE</version>
		</dependency>
		<!-- orm依賴 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>5.2.11.RELEASE</version>
		</dependency>

		<!-- mongodb依賴 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-mongodb</artifactId>
			<version>2.3.3.RELEASE</version>
		</dependency>

		<!-- 依賴的model模組 -->
		<dependency>
			<groupId>com.ycz</groupId>
			<artifactId>ycz-model</artifactId>
			<version>1.0-SNAPSHOT</version>
		</dependency>

		<!-- 依賴api模組 -->
		<dependency>
			<groupId>com.ycz</groupId>
			<artifactId>ycz-api</artifactId>
			<version>1.0-SNAPSHOT</version>
		</dependency>

		<!-- 依賴的web模組 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<version>2.3.6.RELEASE</version>
		</dependency>

		<!-- druid資料來源 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.21</version>
		</dependency>

		<!-- mysql驅動包 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.11</version>
		</dependency>

		<!-- mybatis與spring整合包 -->
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.1</version>
		</dependency>

		<!-- http客戶端 -->
		<dependency>
			<groupId>com.squareup.okhttp3</groupId>
			<artifactId>okhttp</artifactId>
			<version>4.9.0</version>
		</dependency>

		<!-- eureka客戶端依賴 -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
			<version>2.2.2.RELEASE</version>
			<exclusions>
				<!-- 排除掉jackson-core包,否則會衝突 -->
				<exclusion>
					<groupId>com.fasterxml.jackson.core</groupId>
					<artifactId>jackson-core</artifactId>
				</exclusion>
				<!-- 排除到gson包 -->
				<exclusion>
					<groupId>com.google.code.gson</groupId>
					<artifactId>gson</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

		<!-- feign依賴包 -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
			<version>2.2.6.RELEASE</version>
		</dependency>

		<dependency>
			<groupId>io.github.openfeign</groupId>
			<artifactId>feign-okhttp</artifactId>
			<version>11.0</version>
		</dependency>

		<!-- 測試包 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<version>2.3.1.RELEASE</version>
			<scope>test</scope>
		</dependency>

	</dependencies>

下面這兩個依賴是我新新增的,與feign有關:

		<!-- feign依賴包 -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
			<version>2.2.6.RELEASE</version>
		</dependency>

		<dependency>
			<groupId>io.github.openfeign</groupId>
			<artifactId>feign-okhttp</artifactId>
			<version>11.0</version>
		</dependency>

(2)yml配置

這裡只貼關鍵部分:

## eureka配置
eureka:
  ## 客戶端
  client:
    ## 服務註冊開關,需要開啟
    register-with-eureka: true
    ## 服務發現開關,需要開啟
    fetch-registry: true
    ## 向兩個註冊中心中註冊
    service-url:
      defaultZone: http://localhost:20000/eureka/
  ## 例項地址
  instance:
    ## 將自己的IP地址註冊到服務註冊中心
    prefer-ip-address: true
    ip-address: 127.0.0.1
    ## 指定註冊後例項的ID,這裡用工程名:埠號
    instance-id: ${spring.application.name}:${server.port}
    
  # Ribbon配置
ribbon:
    # 最大連線次數,在Eureka中可以找到服務,但是連線不上時會重試連線
  MaxAutoRetries: 5
    #切換例項的重試次數
  MaxAutoRetriesNextServe: 3
    #對所有操作請求都進行重試,如果是get則可以,如果是post,put等操作
    #沒有實現冪等的情況下是很危險的,所以設定為false
  OkToRetryOnAllOperations: false
    # 請求連線的超時時間
  ConnectTimeout: 500000
    # 請求處理的超時時間
  ReadTimeout: 600000

(3)啟動類

@SpringBootApplication
//包掃描
@EntityScan(basePackages = {"com.ycz.domain.person"})
@ComponentScan(basePackages = {"com.ycz.api"})
@ComponentScan(basePackages = {"com.ycz.test.feign"})
@EnableDiscoveryClient
@EnableFeignClients
public class FeignTestApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(FeignTestApplication.class, args);
    }
  

}

這裡加了一個@EnableFeignClients註解,表明這個是Feign的客戶端,需要遠端呼叫別的微服務。簡單來說,A服務想要通過Feign來呼叫B服務,那麼就在A服務的啟動類上新增@EnableFeignClients註解。

(4)微服務列表

全部的微服務名稱可以在一個介面中定義好,那麼呼叫哪個微服務時直接引入這個介面中規定的哪一個就行了,該介面主要是定義微服務名稱的常量:

public interface ServiceInstaceList {
    
    public static final String TEST_SWAGGER = "test-swagger";

}

我這裡只定義了一個微服務。

(5)Feign代理介面

@FeignClient(name = ServiceInstaceList.TEST_SWAGGER)
public interface PersonClient {
    
    @GetMapping("/person/list/{address}")
    QueryResponseResult<Person> findPersonsByAddress(@PathVariable("address") String address);

}

要想呼叫別的微服務,需要定義一個Feign的代理,也就是說Feign來代理請求別的微服務的介面,@FeignClient註解是標明你要呼叫哪個微服務,指定名稱。下面的方法要跟你要呼叫微服務介面一模一樣,其中通過url傳參基本型別必須加@PathVariable註解,傳物件要加@RequestBody轉換。

(6)Service

@Service
public class TestService {
    
    @Autowired
    PersonClient personClient;
    
    public QueryResponseResult<Person> getPersonList(String address){
        return personClient.findPersonsByAddress(address);
    }

}

代理介面定義好了,在Service層裡直接引入就行了。

(7)controller

@RestController
@RequestMapping("/test")
public class TestFeignController implements FeignControllerApi{
    
    @Autowired
    TestService testService;

    @Override
    @GetMapping("/feign/{address}")
    public QueryResponseResult<Person> testFeign(@PathVariable("address") String address) {
        return testService.getPersonList(address);
    }

}

然後這裡的FeignControllerApi如下:

@Api(value = "測試遠端呼叫",description = "呼叫遠端介面",tags = {"測試遠端呼叫"})
public interface FeignControllerApi {
    
    QueryResponseResult<Person> testFeign(String address);

}

介面其實是最先定義好的,在controller中暴露出來。

(8)測試

啟動eureka服務中心,要呼叫的微服務,以及test-feign服務,啟動完畢先看看eureka中心註冊的服務:
在這裡插入圖片描述
兩個微服務都註冊進來了,OK,下面使用swagger測試。

直接進入swagger測試:
在這裡插入圖片描述
輸入引數測試:
**加粗樣式**
返回結果:
在這裡插入圖片描述
返回結果正確,那麼測試就OK了。

4、總結

使用Feign來呼叫遠端微服務介面更加的方便,A呼叫B,在A的啟動類上加上Feign客戶端的註解,然後定義一個代理介面,通過這個代理介面就可以呼叫遠端B服務的介面,HTTP請求由Feign來幫我們傳送,非常好用。

相關文章