SpringCloud OpenFeign的使用舉例詳解
1. OpenFeign
1.1 OpenFeign 的介紹
先來看看咱們之前寫的遠程方法調(diào)用代碼:
@RequestMapping("/ok")
public String ok(@PathParam("content")String content) {
String url = "http://waiter-service/waiter/up/{content}";
String resp = restTemplate.getForObject(url, String.class, content);
return "調(diào)用成功, 已收到 waiter 的響應(yīng): " + resp;
}
雖然說 RestTemplate 對 http 封裝后,使用起來還算方便,但是需要拼接 url,如果 url 很復(fù)雜呢?而且代碼可讀性很差,風格也不好統(tǒng)一。
在微服務(wù)之間的通信方式通常分為:RPC 和 HTTP,至于 RPC 后期有機會在介紹。
在 SpringCloud 中,默認使用的是 HTTP 來進行微服務(wù)的通信,最常用的實現(xiàn)有兩種:
- RestTemplate
- OpenFeign
RestTemplate 咱們已經(jīng)見過了,接下來就學習下 OpenFeign,這是一種更優(yōu)雅的遠程方法調(diào)用的形式。
OpenFeign 是一個聲明式的 Web Service 客戶端,它讓微服務(wù)之間的調(diào)用變得更簡單了。類似于 Controller 調(diào)用 Service,只需要創(chuàng)建一個接口,然后添加注解即可以使用 OpenFeign。
本來是 Feign 是由 Netflix 公司開源的組件,后來呢,在16年7月發(fā)布了最后一個版本,就將捐給了社區(qū),16年7月,OpenFeign 首個版本 9.0.0 發(fā)布后,就一直發(fā)布到現(xiàn)在。
SpringCloud 將 Feign 項目繼承到 SpringCloud 的生態(tài)中,但是受到 Feign 更名的影響,所以 SpringCloudFeign 有兩個 starter。
spring-cloud-starter-feign 和 spring-cloud-starter-openfeign
由于 Feign 停止維護,咱們的項目中使用的是后者 OpenFeign。
1.2 快速使用 OpenFeign
在 cook-service 引入 OpenFeign 依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>在 cook-service 啟動類添加注解:@EnableFeignClients 開啟 OpenFeign 功能。
package com.zlcode.cook;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class CookServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CookServiceApplication.class, args);
}
}編寫 OpenFeign 客戶端,基于 SpringMVC 注解來聲明遠程調(diào)用的信息。
在 cook 目錄下創(chuàng)建 api 目錄,在這個 api 目錄中創(chuàng)建 WaiterApi 接口。
package com.zlcode.cook.api;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient(value = "waiter-service", path = "/waiter")
public interface WaiterApi {
@RequestMapping("/up/{content}")
String up(@PathVariable("content") String content);
}上述 @FeignClient 注解中的 value 表示注冊的服務(wù)名,用于服務(wù)發(fā)現(xiàn),F(xiàn)eign底層會使用 Spring Cloud Load Balance 進行負載均衡,如果使用了 Nacos 負載均衡策略則使用的是 Nacos 的負載均衡。path 表示統(tǒng)一接口前綴,與咱們的 WaiterController 對應(yīng)的。
WaiterController:
package com.zlcode.waiter.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/waiter")
@Slf4j
public class WaiterController {
@RequestMapping("/up/{content}")
public String up(@PathVariable("content") String content) {
log.info("正在執(zhí)行: " + content);
return "執(zhí)行: " + content + "成功!";
}
}修改 CookController 的遠程方法調(diào)用代碼:
package com.zlcode.cook.controller;
import com.zlcode.cook.api.WaiterApi;
import jakarta.websocket.server.PathParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/cook")
public class CookController {
@Autowired
private WaiterApi waiterApi;
@RequestMapping("/ok")
public String ok(@PathParam("content")String content) {
String resp = waiterApi.up(content);
return "調(diào)用成功, 已收到 waiter 的響應(yīng): " + resp;
}
}重啟 cook-service,再去瀏覽器中訪問:http://127.0.0.1:8080/cook/up?content=給25桌上紅燒肉

1.3 接口返回的是自定義對象該怎么辦?
咱們例子中,waiter-service 的 waiter/up/ 接口返回值是 String,每個 Java 項目都可以用 String 這個對象,如果新增一個接口,獲取服務(wù)員信息呢?返回的是一個 WaiterInfo 對象,那么在 cook-service 調(diào)用方該如何接收呢?
其實方法很簡單,咱們先在 WatierController 新增一個 get-info 接口:
package com.zlcode.waiter.controller;
import com.zlcode.waiter.model.WaiterInfo;
import jakarta.websocket.server.PathParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/waiter")
@Slf4j
public class WaiterController {
@RequestMapping("/up/{content}")
public String up(@PathVariable("content") String content) {
log.info("正在執(zhí)行: " + content);
return "執(zhí)行: " + content + "成功!";
}
@RequestMapping("/get-info")
public WaiterInfo getInfo(String name) {
WaiterInfo waiterInfo = new WaiterInfo();
waiterInfo.setWaiterId(1111);
waiterInfo.setWaiterName(name);
return waiterInfo;
}
}在 cook-service 的 WatierApi 接口中新增遠程調(diào)用 waiter-service 提供的 get-info 的接口:

此時發(fā)現(xiàn)咱們的 cook-service 項目中沒有 WaiterInfo 這個類,這也好辦,去 waiter-service 中把 WaiterInfo 類復(fù)制到 cook-service 的 model 目錄中。
package com.zlcode.cook.api;
import com.zlcode.cook.model.WaiterInfo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "waiter-service", path = "/waiter")
public interface WaiterApi {
@RequestMapping("/up/{content}")
String up(@PathVariable("content") String content);
@RequestMapping("/get-info")
WaiterInfo getInfo(@RequestParam("name") String name);
}果然解決了這個問題,再來到 CookController 中新增接口去遠程調(diào)用 get-info 方法:
package com.zlcode.cook.controller;
import com.zlcode.cook.api.WaiterApi;
import com.zlcode.cook.model.WaiterInfo;
import jakarta.websocket.server.PathParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/cook")
public class CookController {
@Autowired
private WaiterApi waiterApi;
@RequestMapping("/ok")
public String ok(@PathParam("content")String content) {
String resp = waiterApi.up(content);
return "調(diào)用成功, 已收到 waiter 的響應(yīng): " + resp;
}
@RequestMapping("/get")
public String get(@RequestParam("name")String name) {
WaiterInfo waiterInfo = waiterApi.getInfo(name);
return "調(diào)用成功, 已收到 waiter 的響應(yīng): " + waiterInfo;
}
}重啟 cook-service 和 waiter-service,在瀏覽器中訪問:http://127.0.0.1:8080/cook/get?name=張三

調(diào)用成功,拿到了 waiter-service get-info 接口返回的 WaiterInfo 對象。
但是咱們仔細想想這段代碼,在 cook-service 項目中存在 WaiterInfo 實體類,在 waiter-service 項目中也存在 WaiterInfo 實體類。那隨著業(yè)務(wù)的增多,這樣的冗余代碼也會越來越多。而且咱們的 WaiterAPI 和 WaiterController 的代碼也十分相似。
所以上述這樣的寫法是不行的,不是最佳的寫法,更好的辦法是把把 OpenFeign 抽離出來,作為一個獨立的模塊,服務(wù)方把提供的 API 都封裝到這個獨立的模塊中,供消費方使用。
1.4 OpenFeign 的最佳實踐
官方推薦的方式是繼承,但是企業(yè)中用的不多,咱們此處只介紹抽離的方式,就像 6.3 最后說的,把 OpenFeign 提取出來成為一個獨立的模塊即可,這個模塊由服務(wù)方去提供。
簡單來說,將 OpenFeign 的 Client 抽取為?個獨?的模塊,并把涉及到的實體類等都放在這個模塊中,打成?個 jar。消費方只需要依賴該 jar 包即可。
步驟如下:
創(chuàng)建一個 Model

引入依賴
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
</dependencies>編寫 API
將 cook-service 的 WaiterApi 和 WatierInfo 直接復(fù)制進來:

將這個 waiter-service-api 打包成 jar 包,單擊 maven 選項中的 install:

將 cook-service 的 WaiterApi 和 WatierInfo 刪除掉,然后引入咱們自己打包的 waiter-service-api 依賴:
<dependency>
<groupId>org.example</groupId>
<artifactId>waiter-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>在啟動類添加掃描路徑:
package com.zlcode.cook;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients(basePackages = "com.zlcode.api")
public class CookServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CookServiceApplication.class, args);
}
}也可以指定需要加載的 Feign客戶端:
@EnableFeignClients(basePackageClasses = WaiterApi.class) @EnableFeignClients(basePackages = "com.zlcode.api")
接著重新啟動 cook-service 項目,然后在瀏覽器中訪問:http://127.0.0.1:8080/cook/get?name=張三

這種更優(yōu)雅的遠程方法調(diào)用也就成功了!
1.5 部署時需要注意事項
由于當前項目中使用到了自己封裝的 jar 包,但是 maven 打包默認會從中央倉庫中去獲取,但是咱們的 waiter-service-api 是在本地,這就比較麻煩,目前有三種解決方案:
- 將 waiter-service-api jar 包上傳到 maven 中央倉庫,但需要注冊申請,比較麻煩。
- 搭建 maven 私服,上傳 jar 包到私服,也是較麻煩的,企業(yè)中比較推薦。
- 從本地讀取 jar 包,這是在個人項目階段中比較推薦的。只需要在引入本地 jar 包的項目中修改 pom.xml 文件就 ok 了。
觀先獲取 waiter-service-api 執(zhí)行 install 后的本地 jar 包路徑(通過控制臺查看):

把之前引入的依賴替換成本地目錄(記得將 \ 換成 /):
<dependency>
<groupId>org.example</groupId>
<artifactId>waiter-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>system</scope>
<systemPath>
C:/xxx/xxx/.m2/repository/org/example/waiter-service-api/1.0-SNAPSHOT/waiter-service-api-1.0-SNAPSHOT.jar
</systemPath>
</dependency>把 build 配置項換成:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
</plugins>
</build>includeSystemScope 允許包括系統(tǒng)本地的 jar 包。
接下來就可以進行快樂的部署到 Linux 服務(wù)器啦,剩下部署的操作這里就不再贅述了。
到此這篇關(guān)于SpringCloud OpenFeign的使用的文章就介紹到這了,更多相關(guān)SpringCloud OpenFeign使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot中實現(xiàn)定時任務(wù)的幾種方式
定時任務(wù)在我們項目開發(fā)中也是很重要的,對于某些場景必須要用定時任務(wù)?,如定時發(fā)送郵件啊,定時統(tǒng)計數(shù)據(jù)等,這篇文章主要講講項目中實現(xiàn)定時任務(wù)的幾種方式,需要的朋友可以參考下2023-05-05
關(guān)于protected修飾符詳解-源于Cloneable接口
這篇文章主要介紹了protected修飾符詳解-源于Cloneable接口,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
Java性能優(yōu)化實戰(zhàn)技巧與最佳實踐分享
性能優(yōu)化是 Java 開發(fā)中永恒的話題,無論是高并發(fā)系統(tǒng)、響應(yīng)式服務(wù)、還是數(shù)據(jù)密集型處理程序,都離不開系統(tǒng)性、可驗證的性能調(diào)優(yōu)方法,本文從 JVM、代碼層、集合框架、多線程、IO、數(shù)據(jù)庫及工具鏈等多維度總結(jié) Java 性能優(yōu)化實戰(zhàn)技巧,需要的朋友可以參考下2025-12-12
java課程設(shè)計做一個多人聊天室(socket+多線程)
這篇文章主要介紹了我的java課程設(shè)計一個多人聊天室(socket+多線程)本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08
java中Scanner.next()和Scanner.nextLine的區(qū)別圖文詳解
使用java語言編程,最常用的輸入就是使用Scanner了,它的構(gòu)造很簡單,這篇文章主要給大家介紹了關(guān)于java中Scanner.next()和Scanner.nextLine區(qū)別的相關(guān)資料,需要的朋友可以參考下2024-02-02

