Eureka registry and OpenFeign calling interface

need

An application calls the interface of another application through the interface. Use OpenFeign to implement interface calls.

illustrate

Calling the remote interface through OpenFeign (hereinafter referred to as Feign in this article) requires the support of the Eureka registry.

The logic of the OpenFeign calling interface is as follows:

  1. The application (A) that provides the interface registers itself with the Eureka server (registration center); application A needs to give itself an application name;
  2. The application (B) that calls the interface reads the information of all registered services from Eureka;
  3. The Feign client of the B application finds the application A (corresponding IP address and port number) from the information of the registered service through the application name of the service, and calls the interface of A.

Main content of this article

This article mainly describes how to configure a registration center (Eureka), the configuration of Feign, and use Feign to call the interface.
It mainly contains three parts:

  1. Configure the Eureka registry (single, non-cluster);
  2. Configure the application that provides the interface, register to Eureka: provide the interface to be called;
  3. Configure the application of the call interface, and obtain the callee address from Eureka: call interface.

Eureka server

1. Dependence

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

2. Configuration (application.properties)

This configuration is a single server configuration, not a cluster configuration.

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. Start the Eureka server

On the Application startup class, add annotations @EnableEurekaServer.

Sample code:

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

An application that provides an interface can call the interface through Feign.

1. Dependence

  • Eureka Discovery Client
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2. Configuration (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. Provide interface

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 client

Through Feign, call the interface of FeignServer application.

1. Dependence

Two dependencies need to be introduced:

  • Eureka Discovery Client
  • OpenFeign
<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>

Note: You need to manage the spring cloud version through <dependencyManagement>and <properties>. If it has already been added to the project, no additional modification is required.

<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. Configuration (application.properties)

server.port=8082

# 不将自身注册到Eureka注册中心。本配置为是否将自己注册到注册中心,默认为true。
eureka.client.registerWithEureka=false
# 注册中心地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

3. Open the Feign client

On the Application startup class, add annotations @EnableFeignClients.

Sample code:

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. Define the interface (corresponding to FeignServer)

Note @FeignClient: Indicates the Feign interface.

name: The application name of the application called by Feign.

path: The common path of all interfaces in FeignClient. Generally, it corresponds to the public interface path of the Controller of the application called by Feign, that is, the interface path in @RequestMapping on the Controller.

Note: In FeignClient, nameand valueare aliases for each other. The name is used in the official website example, and the name field is also used in this example. But after testing, the effect of the value field is exactly the same as the name.

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. Call the Feign interface

Call the Feign interface just like calling a local method.

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);
	}

}


call example

Eureka

insert image description here

The interface of FeignServer is called directly

insert image description here

FeignClient calls the interface of FeignServer through Feign

insert image description here

Add Eureka and Feign dependencies when creating a project

When creating a new SpringBoot project, you can add dependencies through the SpringBoot creator. At this point, in the dependency search box on the lower left side, you can directly search for the dependencies of Eureka and OpenFeign. Check the required dependencies, and the corresponding dependencies will be directly added to the project when creating.

The three dependencies of Eureka and OpenFeign, and their corresponding meanings are as follows:

Eureka Server: Eureka server;
Eureka Discovery Client: Eureka client;
OpenFeign: Feign client;

insert image description here

Configure the application name of the called party in application

The Feign client uses the configuration (placeholder), setting the callee's application name.
In Feign, the name and url attributes support placeholders.

Official website example

insert image description here

code example

insert image description here

FeignClient configuration

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);

}

application configuration

# 被调用的Feign服务的应用名
feign.name=feign-server

contextId: distinguish multiple FeignClients corresponding to the same application

The interface provided by the called application may be divided into several different modules according to the business logic. In the Feign client, each module corresponds to an independent FeignClient.
Because the invocation is the same application, the application names (name field) of multiple FeignClients are the same. contextIdIt is necessary to dispose of different FeignClients through fields; otherwise, conflicts will occur and errors will be reported.

core code

Two FeignClients, use different ones respectively 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 {
    
    
	// 接口定义代码,省略...
}

Error report without 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

reference

  1. official document
    insert image description here

  2. Two FeignClient interfaces of the blog use the same service name to report an error

@RequestParam: Get method parameter annotation

For Feign's Get method, the request parameters need to be @RequestParamannotated.
If no annotation is added, the following two errors will be reported according to the number of parameters.

Two kinds of errors

Body parameter 0 was null

Feign client, when calling the Get method, the interface contains a parameter, and an error is reported:

java.lang.IllegalArgumentException: Body parameter 0 was null

Method has too many Body parameters

Feign client, when calling the Get method, the interface contains multiple parameters, and an error is reported:

Method has too many Body parameters

The original code of the error interface

Body parameter 0 was null

  • Feign server-side interface
	@GetMapping("one_param")
	public String getData(String account) {
    
    
		return "从FeignServer查询的数据!入参为:" + account;
	}
  • Feign client
	@GetMapping("one_param")
	String getData(String account);

Method has too many Body parameters

  • Feign server-side interface
	@GetMapping("two_params")
	public String getDataByTwoParam(String account, String name) {
    
    
		return "从FeignServer查询的数据!account=" + account + ",name=" + name;
	}
  • Feign client
	@GetMapping("two_params")
	public String getDataByTwoParam(String account, String name);

Solution: @RequestParam

Feign interface parameters add @RequestParamannotations.

Feign client, the modified code is as follows:

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);

Complete Feign client code example

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);

}

Interface example of successful call

insert image description here

insert image description here

@SpringQueryMap

Feign's GET interface, 数据类(ie: POJO) as a parameter, uses the @SpringQueryMap annotation to mark the parameter.
If you do not add comments before the parameters @SpringQueryMap, Feign will report an error.

code example

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);

}

report error

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]

official document

Feign @QueryMap support

insert image description here

@SpringQueryMap parameter missing

An interface using @SpringQueryMap, 只能containing 一个a parameter object.
If the interface has two parameter objects, and both are annotated with @SpringQueryMap, the second parameter object 被丢弃will not be parsed into the parameters of the interface request at all.

Print Feign logs

Feign sends request log information

configuration file

# 打印Feign接口调用日志(仅开发测试环境使用)
logging.level.com.kiiik.web=debug
feign.client.config.default.loggerLevel=FULL

Reference article:
Printing Feign logs

url: Access the called application through IP address and port number

@FeignClient can specify the IP address and port number of the house server through the url field. When the called application is not registered to the Eureka registration center, it is enough to configure the actual address directly through the url.

code example

FeignClient: url field

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 {
    
    
    // 接口,省略...
}

configuration

# 被调用的Feign服务的IP地址和端口号(用于调用没有注册到Eureka的服务)
feign.url=http://localhost:8081

Official website document

insert image description here

Download files through Feign call interface

Implementation

It is necessary to download the file through the Feign call interface, and directly let the Feign interface return value Response, the full name feign.Response; and then Responseobtain the input stream. After that, the input stream can be processed, put into the output stream, saved locally or delivered to the user.

code example

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 Official Documentation

Spring Cloud OpenFeign Official Documentation

Spring Cloud OpenFeign official website

Spring Cloud OpenFeign official website

Guess you like

Origin blog.csdn.net/sgx1825192/article/details/131886977