必要
アプリケーションは、インターフェイスを介して別のアプリケーションのインターフェイスを呼び出します。OpenFeign を使用してインターフェイス呼び出しを実装します。
説明する
OpenFeign (以降、この記事では Feign と呼びます) を介してリモート インターフェイスを呼び出すには、Eureka レジストリのサポートが必要です。
OpenFeign 呼び出しインターフェイスのロジックは次のとおりです。
- インターフェースを提供するアプリケーション (A) は、自身を Eureka サーバー (登録センター) に登録します。アプリケーション A は、それ自体にアプリケーション名を付ける必要があります。
- インターフェイスを呼び出すアプリケーション (B) は、登録されているすべてのサービスの情報を Eureka から読み取ります。
- アプリケーション B の Feign クライアントは、登録されたサービスの情報からサービスのアプリケーション名を通じてアプリケーション A (対応する IP アドレスとポート番号) を見つけ、A のインターフェイスを呼び出します。
この記事の主な内容
この記事では主に、登録センター (Eureka) の構成方法、Feign の構成方法、および Feign を使用してインターフェイスを呼び出す方法について説明します。
主に次の 3 つの部分で構成されます。
- Eureka レジストリを構成します (単一、非クラスター)。
- インターフェイスを提供するアプリケーションを構成し、Eureka に登録します。呼び出されるインターフェイスを提供します。
- 呼び出しインターフェースのアプリケーションを設定し、Eureka: 呼び出しインターフェースから呼び出し先アドレスを取得します。
エウレカサーバー
1.依存性
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
2. 設定 (application.properties)
この構成はシングルサーバー構成であり、クラスター構成ではありません。
server.port=8761
# 主机名,不配置的时候将根据操作系统的主机名获取。
eureka.instance.hostname=localhost
# 不将自身注册到注册中心。是否将自己注册到注册中心,默认为true。单个Eureka服务器,不需要注册自身,配置为false;如果是Eureka集群,则需要注册自身,即配置为true。
eureka.client.registerWithEureka=false
# 是否从注册中心获取服务注册信息,默认为true。
eureka.client.fetchRegistry=false
# 注册中心对外暴露的注册地址
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
3. Eurekaサーバーを起動します
アプリケーション起動クラスにアノテーションを追加します@EnableEurekaServer
。
サンプルコード:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerDemoApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerDemoApplication.class, args);
}
}
FeignServer
インターフェイスを提供するアプリケーションは、Feign を通じてインターフェイスを呼び出すことができます。
1.依存性
- エウレカ ディスカバリー クライアント
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2. 設定 (application.properties)
server.port=8081
# 应用名称
spring.application.name=feign-server
# 使用 ip地址:端口号 注册
eureka.instance.prefer-ip-address=true
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
# 注册中心地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
3. インターフェースの提供
package com.example.feign.server.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("feign_server_path")
public class DataController {
@GetMapping("hello")
public String hello() {
return "hello feign server!";
}
@GetMapping("data")
public String getData() {
return "来自FeignServer的数据!";
}
@GetMapping("result")
public String getData(String account) {
return "从FeignServer查询的数据!入参为:" + account;
}
@GetMapping("two_params")
public String getDataByTwoParam(String account, String name) {
return "从FeignServer查询的数据!account=" + account + ",name=" + name;
}
}
偽のクライアント
Feign を通じて、FeignServer アプリケーションのインターフェースを呼び出します。
1.依存性
次の 2 つの依存関係を導入する必要があります。
- エウレカ ディスカバリー クライアント
- オープンフェイン
<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>
注: Spring Cloud バージョンは<dependencyManagement>
とを通じて管理する必要があります<properties>
。すでにプロジェクトに追加されている場合は、追加の変更は必要ありません。
<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>
<properties>
<spring-cloud.version>2021.0.8</spring-cloud.version>
</properties>
2. 設定 (application.properties)
server.port=8082
# 不将自身注册到Eureka注册中心。本配置为是否将自己注册到注册中心,默认为true。
eureka.client.registerWithEureka=false
# 注册中心地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
3. Feign クライアントを開きます
アプリケーション起動クラスにアノテーションを追加します@EnableFeignClients
。
サンプルコード:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients
@SpringBootApplication
public class FeignClientDemoApplication {
public static void main(String[] args) {
SpringApplication.run(FeignClientDemoApplication.class, args);
}
}
4. インターフェースの定義(FeignServerに相当)
注@FeignClient
: Feign インターフェイスを示します。
name
: Feign によって呼び出されるアプリケーションのアプリケーション名。
path
: FeignClient のすべてのインターフェイスの共通パス。一般的には、Feign によって呼び出されるアプリケーションのコントローラーのパブリック インターフェイス パス、つまりコントローラー上の @RequestMapping 内のインターフェイス パスに対応します。
注: FeignClient では、name
と はvalue
相互のエイリアスです。名前は公式 Web サイトの例で使用されており、この例でも名前フィールドが使用されています。しかし、テスト後、値フィールドの効果は名前とまったく同じです。
package com.example.feign.client.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "feign-server", path = "feign_server_path")
public interface DataClient {
@GetMapping("data")
String getData();
@GetMapping("result")
String getDataByOneParam(@RequestParam("account") String account);
@GetMapping("two_params")
public String getDataByTwoParam(@RequestParam("account") String account, @RequestParam("name") String name);
}
5. Feign インターフェイスを呼び出す
ローカル メソッドを呼び出すのと同じように、Feign インターフェイスを呼び出します。
package com.example.feign.client.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.feign.client.feign.DataClient;
@RestController
@RequestMapping("feign_client")
public class FeignClientDataController {
@GetMapping("hello")
public String hello() {
return "hello feign client!";
}
@Autowired
private DataClient client;
@GetMapping("data")
public String getData() {
return "通过FeignClient调用:" + client.getData();
}
@GetMapping("one_param")
public String getDataByOneParam(String account) {
return "通过FeignClient调用:" + client.getDataByOneParam(account);
}
@GetMapping("two_params")
public String getDataByTwoParam(String account, String name) {
return "通过FeignClient调用:" + client.getDataByTwoParam(account, name);
}
}
電話の例
ユーレカ
FeignServerのインターフェースを直接呼び出す
FeignClient は Feign を通じて FeignServer のインターフェースを呼び出します。
プロジェクトの作成時に Eureka と Feign の依存関係を追加する
新しい SpringBoot プロジェクトを作成するときは、SpringBoot クリエーターを通じて依存関係を追加できます。この時点で、左下の依存関係検索ボックスで、Eureka と OpenFeign の依存関係を直接検索できます。必要な依存関係を確認すると、対応する依存関係が作成時にプロジェクトに直接追加されます。
Eureka と OpenFeign の 3 つの依存関係と、それらの対応する意味は次のとおりです。
Eureka Server
: エウレカサーバー;
Eureka Discovery Client
: エウレカクライアント;
OpenFeign
: 偽クライアント;
アプリケーションで着信側のアプリケーション名を設定します
Feign クライアントは構成 (プレースホルダー) を使用して、呼び出し先のアプリケーション名を設定します。
Feign では、name 属性と url 属性がプレースホルダーをサポートします。
公式サイトの例
コード例
FeignClient の構成
package com.example.feign.client.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "${feign.name}", path = "feign_server_path")
public interface DataClient {
@GetMapping("data")
String getData();
@GetMapping("one_param")
String getDataByOneParam(@RequestParam("account") String account);
@GetMapping("two_params")
public String getDataByTwoParam(@RequestParam("account") String account, @RequestParam("name") String name);
}
アプリケーション構成
# 被调用的Feign服务的应用名
feign.name=feign-server
contextId: 同じアプリケーションに対応する複数の FeignClient を区別します。
呼び出されたアプリケーションによって提供されるインターフェイスは、ビジネス ロジックに従っていくつかの異なるモジュールに分割される場合があります。Feign クライアントでは、各モジュールが独立した FeignClient に対応します。
呼び出しは同じアプリケーションであるため、複数の FeignClient のアプリケーション名 (名前フィールド) は同じです。フィールドを通じてcontextId
さまざまな FeignClient を破棄する必要があります。そうしないと、競合が発生し、エラーが報告されます。
コアコード
2 つの FeignClient は、それぞれ異なるものを使用しますcontextId
。
package com.example.feign.client.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "${feign.name}", path = "feign_server_path", contextId = "DataClient")
public interface DataClient {
// 接口定义代码,省略...
}
package com.example.feign.client.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(name = "${feign.name}", path = "files", contextId = "FileClient")
public interface FileClient {
// 接口定义代码,省略...
}
contextId のないエラー レポート
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
[2m2023-08-06 23:27:20.184[0;39m [31mERROR[0;39m [35m14592[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.s.b.d.LoggingFailureAnalysisReporter [0;39m [2m:[0;39m
***************************
APPLICATION FAILED TO START
***************************
Description:
The bean '${feign.name}.FeignClientSpecification' could not be registered. A bean with that name has already been defined and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
参考
@RequestParam: メソッドパラメータのアノテーションを取得
Feign の Get メソッドの場合、リクエスト パラメーターに@RequestParam
注釈を付ける必要があります。
アノテーションが追加されていない場合、パラメータの数に応じて次の 2 つのエラーが報告されます。
2種類のエラー
本体パラメータ 0 が null でした
偽のクライアントが Get メソッドを呼び出すと、インターフェイスにパラメーターが含まれており、エラーが報告されます。
java.lang.IllegalArgumentException: 本体パラメータ 0 が null でした
メソッドの Body パラメータが多すぎます
偽のクライアントが Get メソッドを呼び出すと、インターフェイスに複数のパラメーターが含まれており、エラーが報告されます。
メソッドの Body パラメータが多すぎます
エラーインターフェイスの元のコード
本体パラメータ 0 が null でした
- サーバー側インターフェースを偽装する
@GetMapping("one_param")
public String getData(String account) {
return "从FeignServer查询的数据!入参为:" + account;
}
- 偽のクライアント
@GetMapping("one_param")
String getData(String account);
メソッドの Body パラメータが多すぎます
- サーバー側インターフェースを偽装する
@GetMapping("two_params")
public String getDataByTwoParam(String account, String name) {
return "从FeignServer查询的数据!account=" + account + ",name=" + name;
}
- 偽のクライアント
@GetMapping("two_params")
public String getDataByTwoParam(String account, String name);
解決策: @RequestParam
Feign インターフェイス パラメーターは@RequestParam
注釈を追加します。
偽クライアントの場合、変更されたコードは次のとおりです。
import org.springframework.web.bind.annotation.RequestParam;
@GetMapping("result")
String getData(@RequestParam("account") String account);
@GetMapping("two_params")
public String getDataByTwoParam(@RequestParam("account") String account, @RequestParam("name") String name);
Feign クライアントの完全なコード例
package com.example.feign.client.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "feign-server", path = "feign_server_path")
public interface FeignInvocationService {
@GetMapping("data")
String getFeignServerData();
@GetMapping("result")
String getData(@RequestParam("account") String account);
@GetMapping("two_params")
public String getDataByTwoParam(@RequestParam("account") String account, @RequestParam("name") String name);
}
成功した呼び出しのインターフェイスの例
@SpringQueryMap
Feign の GET インターフェイス数据类
(つまり、POJO) をパラメーターとして使用し、 @SpringQueryMap アノテーションを使用してパラメーターをマークします。
パラメータの前にコメントを追加しないと@SpringQueryMap
、Feign はエラーを報告します。
コード例
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.web.bind.annotation.GetMapping;
import com.example.feign.client.feign.query.InputQuery;
@FeignClient(name = "${feign.name}", path = "feign_server_path", contextId = "DataClient")
public interface DataClient {
// 其他接口...
@GetMapping("query_object")
public String getDataByQueryObject(@SpringQueryMap InputQuery query);
}
エラーを報告する
2023-08-07 23:06:25.570[0;39m [31mERROR[0;39m [35m9368[0;39m [2m---[0;39m [2m[nio-8082-exec-3][0;39m [36mo.a.c.c.C.[.[.[/].[dispatcherServlet] [0;39m [2m:[0;39m Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is feign.FeignException$MethodNotAllowed: [405] during [GET] to [http://feign-server/feign_server_path/query_object] [DataClient#getDataByQueryObject(InputQuery)]: [{"timestamp":"2023-08-07T15:06:25.532+00:00","status":405,"error":"Method Not Allowed","path":"/feign_server_path/query_object"}]] with root cause
feign.FeignException$MethodNotAllowed: [405] during [GET] to [http://feign-server/feign_server_path/query_object] [DataClient#getDataByQueryObject(InputQuery)]: [{"timestamp":"2023-08-07T15:06:25.532+00:00","status":405,"error":"Method Not Allowed","path":"/feign_server_path/query_object"}]
at feign.FeignException.clientErrorStatus(FeignException.java:221) ~[feign-core-11.10.jar:na]
at feign.FeignException.errorStatus(FeignException.java:194) ~[feign-core-11.10.jar:na]
at feign.FeignException.errorStatus(FeignException.java:185) ~[feign-core-11.10.jar:na]
公式文書
@SpringQueryMap パラメータがありません
@SpringQueryMap を使用するインターフェイス。パラメーター オブジェクトが只能
含まれます。インターフェイスに 2 つのパラメーター オブジェクトがあり、両方に @SpringQueryMap アノテーションが付けられている場合、2 番目のパラメーター オブジェクトはインターフェイス リクエストのパラメーターに解析されますが、まったく解析されません。一个
被丢弃
偽のログを印刷する
Feign はリクエストログ情報を送信します
設定ファイル
# 打印Feign接口调用日志(仅开发测试环境使用)
logging.level.com.kiiik.web=debug
feign.client.config.default.loggerLevel=FULL
参考記事:
Print Feignログ
url: IP アドレスとポート番号を使用して呼び出されたアプリケーションにアクセスします
@FeignClient は、URL フィールドを通じてハウスサーバーの IP アドレスとポート番号を指定できます。呼び出されるアプリケーションが Eureka 登録センターに登録されていない場合は、URL を通じて実際のアドレスを直接構成するだけで十分です。
コード例
FeignClient: URL フィールド
package com.example.feign.client.feign;
import org.springframework.cloud.openfeign.FeignClient;
@FeignClient(name = "${feign.name}", url = "${feign.url}", path = "feign_server_path", contextId = "DataClient")
public interface DataClient {
// 接口,省略...
}
構成
# 被调用的Feign服务的IP地址和端口号(用于调用没有注册到Eureka的服务)
feign.url=http://localhost:8081
公式ウェブサイトのドキュメント
Feign 通話インターフェイスを介してファイルをダウンロードする
実装
Response
Feign 呼び出しインターフェイスを介してファイルをダウンロードし、Feign インターフェイスに値、フルネームを直接返してからfeign.Response
、Response
入力ストリームを取得する必要があります。その後、入力ストリームを処理したり、出力ストリームに入れたり、ローカルに保存したり、ユーザーに配信したりできます。
コード例
package com.example.feign.client.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import feign.Response;
@FeignClient(name = "${feign.name}", url = "${feign.url}", path = "files", contextId = "FileClient")
public interface FileClient {
@GetMapping("download")
Response download();
@GetMapping("/download/{filename}")
Response download(@PathVariable("filename") String filename);
}
package com.example.feign.client.controller;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.feign.client.feign.FileClient;
@RestController
@RequestMapping("files")
public class FileController {
@Autowired
private FileClient client;
@GetMapping("download")
public void download(HttpServletResponse response) throws IOException {
InputStream inputStream = client.download().body().asInputStream();
String fileName = URLEncoder.encode("测试文件.txt", "UTF-8");
response.setHeader("content-disposition", "attachment;fileName=" + fileName);
ServletOutputStream outputStream = response.getOutputStream();
IOUtils.copy(inputStream, outputStream);
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
}
@GetMapping("/download/{filename}")
public void downloadByPathname(@PathVariable("filename") String filename, HttpServletResponse response)
throws IOException {
InputStream inputStream = client.download(filename).body().asInputStream();
String fileName = URLEncoder.encode(filename, "UTF-8");
response.setHeader("content-disposition", "attachment;fileName=" + fileName);
ServletOutputStream outputStream = response.getOutputStream();
IOUtils.copy(inputStream, outputStream);
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
}
}
Spring Cloud OpenFeign 公式ドキュメント
Spring Cloud OpenFeign 公式ドキュメント