分布式遠程調用SpringCloud-Feign的兩種具體操作方式
一 前言
幾大RPC框架介紹
spring cloud遠程調用方式---Feign
Feign是一個聲明似的web服務客戶端,它使得編寫web服務客戶端變得更加容易。使用Fegin創建一個接口並對它進行注解。它具有可插拔的注解支持包括Feign注解與JAX-RS注解,Feign還支持可插拔的編碼器與解碼器,Spring Cloud 增加了對 Spring MVC的注解,Spring Web 默認使用了HttpMessageConverters, Spring Cloud 集成 Ribbon 和 Eureka 提供的負載均衡的HTTP客戶端 Feign。

官方解釋: Feign is a Java to HTTP client binder inspired by Retrofit, JAXRS-2.0, and WebSocket. Feign's first goal was reducing the complexity of binding Denominator uniformly to HTTP APIs regardless of ReSTfulness.
Feign的兩種調用方式
1 直接在調用者聲明Feign客戶端
如下圖所示, service-b 聲明一個接口, 去調用路徑為 /user/get 的服務 service-a 的服務

2 在被調用者接口Api中聲明Feign客戶端
如下圖, 在被調用者中聲明接口, 去調用自己, 這種方法遵循面向接口編程, 而且使用起來, 就類似dubbo一樣, @Autowire直接注入就可以使用了.
以上可能看得讀者一頭霧水, 以下具體的代碼流程, 可以方便更加具體的了解
二 案例1直接在調用者聲明Feign客戶端代碼實現
以下步驟為手把手教學, 請明白我的良苦用心
步驟0 創建一個SpringCloud-Eureka注冊中心
首先創建一個父項目
把其他都刪除, 剩下pom文件

以下為父項目的依賴
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.fegin</groupId>
<artifactId>test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<!--springboot version 2.1.4-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<!--springcloud version Greenwish.SR1-->
<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>
</project>
創建eureka項目


項目結構如圖

添加eureka的依賴
<?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">
<parent>
<artifactId>test</artifactId>
<groupId>com.fegin</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka</artifactId>
<!--eureka服務端配置-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
添加application.yml
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
# 是否把自己作為服務注冊到其他服務注冊中心
registerWithEureka: false
# 是否從其他的服務中心同步服務列表
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
# 關閉保護機制,默認true
enable-self-preservation: false
# 剔除失效服務間隔,默認60000
eviction-interval-timer-in-ms: 3000
添加啟動類代碼EurekaApplication
/**
* @author c-can-z
*/
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
瀏覽器輸入 http://localhost:8761/

步驟1 准備一個服務servicea --- 該服務為被調用者
步驟2 准備一個服務serviceb --- 該服務為調用者
創建一個serviceb

添加一下serviceb必須的依賴
<?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">
<parent>
<artifactId>test</artifactId>
<groupId>com.fegin</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>serviceb</artifactId>
<dependencies>
<!--springboot web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--springboot 測試-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--eureka客戶端配置-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<!--微服務調用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--zipkin客戶端配置, 已經包含sleuth-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
</dependencies>
</project>
把 application.properties 修改為 application.yml, 本服務名稱為 service-b 端口為 9992
server:
port: 9992
spring:
application:
name: service-b
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
probability: 1
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
registry-fetch-interval-seconds: 5 #eureka client刷新本地緩存時間,默認30
instance:
prefer-ip-address: true
#Eureka客戶端向服務端發送心跳的時間間隔,單位為秒(客戶端告訴服務端自己會按照該規則),默認30
lease-renewal-interval-in-seconds: 5
#Eureka服務端在收到最后一次心跳之后等待的時間上限,單位為秒,超過則剔除(客戶端告訴服務端按照此規則等待自己),默認90
lease-expiration-duration-in-seconds: 7
feign:
client:
config:
default:
connectTimeout: 7000
readTimeout: 7000
service-b:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
logging:
level:
root: info
導入啟動類文件
@SpringBootApplication
@EnableFeignClients
public class SeriveBApplication {
public static void main(String[] args) {
SpringApplication.run(SeriveBApplication.class,args);
}
}
創建Feign客戶端
//在創建該步驟的時候, 需要關注一下步驟1的說明
//@FeignClient(name = "service-a")注解來綁定該接口對應servic-a服務
@FeignClient(name = "service-a")
public interface UserFeginClient {
//service-a服務對應資源路徑.必須加上@RequestParam, 否則會報錯,返回參數也必須對應上
@RequestMapping("user/get")
String get(@RequestParam("id")Long id);
}
在controller中直接進行調用
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private UserFeginClient userFeginClient;
@RequestMapping("/get")
public String get(Long id){
return "產品服務抽獎: "+userFeginClient.get(id);
}
}
步驟3 測試Feign調用效果
三 案例2 在被調用者接口Api中聲明Feign客戶端代碼實現
步驟1 創建servicecapi, 該Api用來創建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">
<parent>
<artifactId>test</artifactId>
<groupId>com.fegin</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>servicec-api</artifactId>
<dependencies>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--微服務調用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>
實體類product
public class Product implements Serializable {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
feign客戶端
/**
* 服務名稱
* @author c-can-z
*/
@FeignClient(name="service-c")
public interface ProductFeignApi {
//動態代理需要的地址, 但是我們實際操作不到
@RequestMapping("/servicec/get")
Product get(@RequestParam("id") Long id);
}
步驟2 創建servicec服務
項目結構

service項目依賴
<?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">
<parent>
<artifactId>test</artifactId>
<groupId>com.fegin</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>servicec</artifactId>
<dependencies>
<!--springboot web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--eureka客戶端配置-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--zipkin客戶端配置, 已經包含sleuth-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<!--springboot 測試-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--導入api-->
<dependency>
<groupId>com.fegin</groupId>
<artifactId>servicec-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
servicec的 application.yml
server:
port: 9993
spring:
application:
name: service-c
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
probability: 1
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
registry-fetch-interval-seconds: 5 #eureka client刷新本地緩存時間,默認30
instance:
prefer-ip-address: true
#Eureka客戶端向服務端發送心跳的時間間隔,單位為秒(客戶端告訴服務端自己會按照該規則),默認30
lease-renewal-interval-in-seconds: 5
#Eureka服務端在收到最后一次心跳之后等待的時間上限,單位為秒,超過則剔除(客戶端告訴服務端按照此規則等待自己),默認90
lease-expiration-duration-in-seconds: 7
feign:
client:
config:
default:
connectTimeout: 7000
readTimeout: 7000
logging:
level:
root: info
實現Feign客戶端接口, 也是該文章的核心代碼
采用實現的方式
/**
* 遠程調用接口的實現類
* @author c-can-z
*/
@RestController
public class ProductFeignClient implements ProductFeignApi {
@Override
public Product get(Long id) {
Product product = new Product();
product.setId(id);
product.setName("我是服務C");
return product;
}
}
servicec的啟動類
@SpringBootApplication
public class ProductServerApplication {
public static void main(String[] args) {
SpringApplication.run(ProductServerApplication.class, args);
}
}
步驟3 創建serviced服務去調用servicec
項目結構
注意啟動類的位置, servicecapi的路徑必須被啟動類掃描到

serviced的依賴,
注意, 必須引入servicec的api, 有人會說有代碼侵入的問題, 但是對比案例1, 如果多個項目調用, 要創建多個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">
<parent>
<artifactId>test</artifactId>
<groupId>com.fegin</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>serviced</artifactId>
<dependencies>
<!--springboot web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--springboot 測試-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--eureka客戶端配置-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--微服務調用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--zipkin客戶端配置, 已經包含sleuth-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>com.fegin</groupId>
<artifactId>servicec-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
serviced的 application.yml
server:
port: 9994
spring:
application:
name: service-d
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
probability: 1
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
registry-fetch-interval-seconds: 5 #eureka client刷新本地緩存時間,默認30
instance:
prefer-ip-address: true
#Eureka客戶端向服務端發送心跳的時間間隔,單位為秒(客戶端告訴服務端自己會按照該規則),默認30
lease-renewal-interval-in-seconds: 5
#Eureka服務端在收到最后一次心跳之后等待的時間上限,單位為秒,超過則剔除(客戶端告訴服務端按照此規則等待自己),默認90
lease-expiration-duration-in-seconds: 7
feign:
client:
config:
default:
connectTimeout: 7000
readTimeout: 7000
logging:
level:
root: info
serviced的控制類
/**
* @author c-can-z
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private ProductFeignApi productFeignApi;
@RequestMapping("/get")
public String get(Long id){
Product product = productFeignApi.get(id);
return "訂單為: 貨品:" + product.getName() + ", 貨品id:"+product.getId();
}
}
serviced的啟動類
/**
* @author c-can-z
*/
@SpringBootApplication
@EnableFeignClients
public class ServiceDApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceDApplication.class,args);
}
}
步驟4 測試Feign調用效果
http://localhost:9994/order/get?id=5

四 總結
到了這里, 不知道你是否理解 直接在調用者聲明Feign客戶端 或者 在被調用者接口Api中聲明Feign客戶端
直接在調用者聲明Feign客戶端:
每一個服務調用其他服務, 就需要創建一個客戶端, 從代碼方面來說, 相對比較麻煩
在被調用者接口Api中聲明Feign客戶端:
從調用者來看, 使用起來就跟使用淘寶的dubbo一樣方便, 但是每一個服務調用其他服務, 就需要引入其他服務的api依賴, 從項目之間的互相依賴來看, 相對來說, 也會比較麻煩.


