相關
前言
上一篇文章,講述瞭如何通過 RestTemplate
配合 Ribbon
去消費服務。Feign
是一個 宣告式 的 HTTP
偽客戶端,提供 面向介面 的 HTTP
客戶端呼叫 程式設計。本文進一步講如何通過 Feign
去消費服務。
-
Feign
只需要建立一個 介面 並提供 註解 即可呼叫。 -
Feign
具有 可插拔 的註解特性,可使用Feign
註解 和JAX-RS
註解。 -
Feign
支援 可插拔 的 編碼器 和 解碼器。 -
Feign
預設整合了Ribbon
,可以和Eureka
結合使用,預設實現了 負載均衡 的效果。
正文
1. 建立服務契約模組
建立一個 service-contract
的專案 Module
,建立完成後的 pom.xml
如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>io.ostenant.github.springcloud</groupId>
<artifactId>service-contract</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>service-contract</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.ostenant.github.springcloud</groupId>
<artifactId>service-contract</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
複製程式碼
在 service-contract
中定義 業務介面 和相關的 DTO
物件如下:
User.java
public class User implements Serializable {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public void getName() {
return this.name;
}
public String setName() {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
複製程式碼
UserContract.java
UserContract
定義了 User
的所有行為,是一個使用 @FeignClient
註解標記的 宣告式服務介面。其中,@FeignClient
的 value
指定的是 服務提供者 的 服務名稱。
@FeignClient("service-provider")
public interface UserContract {
@PostMapping("/user")
void add(@RequestBody User user);
@GetMapping("/user/{name}")
User findByName(@PathVariable String name);
@GetMapping("/users")
List<User> findAll();
}
複製程式碼
對於 服務提供者 而言,需要實現 UserContract
介面的方法;對於 服務消費者 而言,可以直接注入 UserContract
作為 客戶端樁 使用。
2. 建立服務提供者
建立一個 service-provider
的專案 Module
,建立完成後引入 服務契約模組 的依賴,pom.xml
如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>io.ostenant.github.springcloud</groupId>
<artifactId>service-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>service-provider</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Dalston.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.ostenant.github.springcloud</groupId>
<artifactId>service-contract</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
複製程式碼
通過 註解 @EnableEurekaClient
表明自己是一個 Eureka Client
。
@SpringBootApplication
@EnableEurekaClient
@RestController
public class ServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceProviderApplication.class, args);
}
}
複製程式碼
建立一個類 UserService
,實現 UserContract
介面的具體業務,對外提供 User
相關的 HTTP
的服務。
@RestController
public class UserService implements UserContract {
private static final Set<User> userSet = new HashSet<>();
static {
userSet.add(new User("Alex", 28));
userSet.add(new User("Lambert", 32));
userSet.add(new User("Diouf", 30));
}
@Override
public void add(@RequestBody User user) {
userSet.add(user);
}
@Override
public User findByName(@PathVariable String name) {
return userSet.stream().filter(user -> {
return user.getName().equals(name);
}).findFirst();
}
@Override
public List<User> findAll() {
return new ArrayList<>(userSet);
}
}
複製程式碼
在 配置檔案 中註明的 服務註冊中心 的地址,application.yml
配置檔案如下:
spring:
active:
profiles: sp1 # sp2
---
spring:
profiles: sp1
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
server:
port: 8770
spring:
application:
name: service-provider
---
spring:
profiles: sp2
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
server:
port: 8771
spring:
application:
name: service-provider
複製程式碼
分別以 spring.profiles.active=sp1
和 spring.profiles.active=sp2
作為 Spring Boot
的 啟動命令引數,在 埠號 8770
和 8771
啟動 2
個 服務提供者 例項。
3. 建立服務消費者
新建一個專案 Module
,取名為 service-consumer
,在它的 pom
檔案中引入 Feign
的 起步依賴 和 服務契約模組,程式碼如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>io.ostenant.github.springcloud</groupId>
<artifactId>service-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>service-consumer</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Dalston.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.ostenant.github.springcloud</groupId>
<artifactId>service-contract</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
複製程式碼
在專案的配置檔案 application.yml
檔案,指定 應用名稱 為 service-consumer
,埠號 為 8772
,服務註冊地址 為http://localhost:8761/eureka/
,程式碼如下:
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
server:
port: 8772
spring:
application:
name: service-feign
複製程式碼
在應用的啟動類 ServiceConsumerApplication
上加上 @EnableFeignClients
註解開啟 Feign
的功能。
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ServiceConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceConsumerApplication.class, args);
}
}
複製程式碼
定義一個 UserController
控制器,用於呼叫 服務提供者 提供的服務並 響應 前端。
@RestController
public class UserController {
@Autowired
private UserContract userContract;
@PostMapping("/user")
public void add(@RequestBody User user) {
userContract.add(user);
}
@GetMapping("/user/{name}")
public User findByName(@PathVariable String name) {
return userContract.findByName(name);
}
@GetMapping("/users")
public List<User> findAll() {
return userContract.findAll();
}
}
複製程式碼
在 控制層 UserController
引入 Feign
介面,通過 @FeignClient
(服務名稱),來指定呼叫的是哪個 服務。
啟動 服務消費者 應用,訪問 http://localhost:8772/users
測試 服務消費者 的訪問連通性,響應內容為:
[
{
"name": "Alex",
"age": 28
},
{
"name": "Lambert",
"age": 32
},
{
"name": "Diouf",
"age": 30
}
]
複製程式碼
4. Feign的原始碼實現過程
總的來說,Feign
的 原始碼實現 過程如下:
-
首先通過
@EnableFeignClients
註解開啟FeignClient
的功能。只有這個 註解 存在,才會在程式啟動時 啟動@FeignClient
註解 的 包掃描。 -
服務提供者 實現基於
Feign
的 契約介面,並在 契約介面 上面加上@FeignClient
註解。 -
服務消費者 啟動後,會進行 包掃描 操作,掃描所有的
@FeignClient
的 註解 的類,並將這些資訊注入Spring
上下文中。 -
當 介面 的方法被呼叫時,通過
JDK
的 代理 來生成具體的RequestTemplate
模板物件。 -
根據
RequestTemplate
再生成HTTP
請求的Request
物件。 -
Request
物件交給Client
去處理,其中Client
內嵌的 網路請求框架 可以是HTTPURLConnection
、HttpClient
和OkHttp
。 -
最後
Client
被封裝到LoadBalanceClient
類,這個類結合Ribbon
完成 負載均衡 功能。
參考
- 方誌朋《深入理解Spring Cloud與微服務構建》
歡迎關注技術公眾號: 零壹技術棧
本帳號將持續分享後端技術乾貨,包括虛擬機器基礎,多執行緒程式設計,高效能框架,非同步、快取和訊息中介軟體,分散式和微服務,架構學習和進階等學習資料和文章。