上一篇文章,講述瞭如何經過 RestTemplate
配合 Ribbon
去消費服務。Feign
是一個 聲明式 的 HTTP
僞客戶端,提供 面向接口 的 HTTP
客戶端調用 編程。本文進一步講如何經過 Feign
去消費服務。
Feign
只須要建立一個 接口 並提供 註解 便可調用。
Feign
具備 可插拔 的註解特性,可以使用 Feign
註解 和 JAX-RS
註解。
Feign
支持 可插拔 的 編碼器 和 解碼器。
Feign
默認集成了 Ribbon
,能夠和 Eureka
結合使用,默認實現了 負載均衡 的效果。
建立一個 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
做爲 客戶端樁 使用。
建立一個 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
個 服務提供者 實例。
新建一個項目 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
}
]
複製代碼
總的來講,Feign
的 源碼實現 過程以下:
首先經過 @EnableFeignClients
註解開啓 FeignClient
的功能。只有這個 註解 存在,纔會在程序啓動時 啓動 @FeignClient
註解 的 包掃描。
服務提供者 實現基於 Feign
的 契約接口,並在 契約接口 上面加上 @FeignClient
註解。
服務消費者 啓動後,會進行 包掃描 操做,掃描全部的 @FeignClient
的 註解 的類,並將這些信息注入 Spring
上下文中。
當 接口 的方法被調用時,經過 JDK
的 代理 來生成具體的 RequestTemplate
模板對象。
根據 RequestTemplate
再生成 HTTP
請求的 Request
對象。
Request
對象交給 Client
去處理,其中 Client
內嵌的 網絡請求框架 能夠是 HTTPURLConnection
、HttpClient
和 OkHttp
。
最後 Client
被封裝到 LoadBalanceClient
類,這個類結合 Ribbon
完成 負載均衡 功能。
歡迎關注技術公衆號: 零壹技術棧
本賬號將持續分享後端技術乾貨,包括虛擬機基礎,多線程編程,高性能框架,異步、緩存和消息中間件,分佈式和微服務,架構學習和進階等學習資料和文章。