一、简介

Feign是一个由Netflix开源的声明式HTTP客户端框架,旨在简化Web服务客户端的开发。通过抽象化模板代码,Feign让开发者能够以更简洁、更直观的方式编写REST API调用。

二、原生Feign样例

1、注解

  • @RequestLine

    @RequestLine是原生Feign​核心库中用于定义HTTP请求的关键注解,它提供了一种更接近于底层HTTP协议的、声明式的方式来指定请求方法(GET, POST 等)和路径;它的核心作用是将接口方法映射到具体的HTTP请求。

    @RequestLine注解的值是一个字符串,标准格式为:”HTTP_METHOD URI_TEMPLATE”,其中:

    • HTTP_METHOD:请求方法,例如 GET, POST, PUT, DELETE, HEAD等。

    • URI_TEMPLATE:请求路径,可以包含占位符(如{id})来动态替换参数。

  • @Param

使用@Param注解显式标记方法参数,并指定在URI模板或头信息中对应的占位符名称。

举例:

简单的GET请求,无参数:

@RequestLine("GET /books")
List<Book> findAll();

路径中包含参数的GET请求:

@RequestLine("GET /books/{isbn}")
Book findByIsbn(@Param("isbn") String isbn);

路径和查询参数结合使用:

@RequestLine("GET /books?author={author}&title={title}")
List<Book> findBooksByAuthorAndTitle(@Param("author") String author, @Param("title") String title);

POST请求,需指定Content-Type头信息:

@RequestLine("POST /book")
@Headers("Content-Type: application/json")
void create(Book book);

2、样例

  • 数据源

此处使用jsonplaceholder提供的todos数据:

https://jsonplaceholder.typicode.com/todos返回200个待办事项,结构如下:

[
  {
    "userId": 1,
    "id": 1,
    "title": "delectus aut autem",
    "completed": false
  },
  {
    "userId": 1,
    "id": 2,
    "title": "quis ut nam facilis et officia qui",
    "completed": false
  },
  {
    "userId": 1,
    "id": 3,
    "title": "fugiat veniam minus",
    "completed": false
  },
  ...
]

可以在请求中增加todo的id获取指定的待办事项:https://jsonplaceholder.typicode.com/todos/5

{
  "userId": 1,
  "id": 5,
  "title": "laboriosam mollitia et enim quasi adipisci quia provident illum",
  "completed": false
}
  • 依赖

pom.xml中依赖如下:

<dependencies>
	<dependency>
		<groupId>io.github.openfeign</groupId>
		<artifactId>feign-okhttp</artifactId>
		<version>13.1</version>
	</dependency>
	<dependency>
		<groupId>io.github.openfeign</groupId>
		<artifactId>feign-gson</artifactId>
		<version>13.1</version>
	</dependency>
	<dependency>
		<groupId>io.github.openfeign</groupId>
		<artifactId>feign-slf4j</artifactId>
		<version>13.1</version>
	</dependency>
	
	<dependency>
		<groupId>ch.qos.logback</groupId>
		<artifactId>logback-classic</artifactId>
		<version>1.2.13</version>
	</dependency>
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>slf4j-api</artifactId>
		<version>1.7.32</version>
	</dependency>
</dependencies>
  • 日志配置

src/main/resource/logback.xml

<configuration>
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="DEBUG"> <!-- 根据需要调整根日志级别,如 INFO 或 WARN -->
    <appender-ref ref="CONSOLE" />
  </root>
</configuration>
  • 模型类

Todo.java:

public class Todo {

	private int id;
	private int userId;
	private String title;
	private boolean completed;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public int getUserId() {
		return userId;
	}

	public void setUserId(int userId) {
		this.userId = userId;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public boolean isCompleted() {
		return completed;
	}

	public void setCompleted(boolean completed) {
		this.completed = completed;
	}

	@Override
	public String toString() {
		return String.format("id: %s, userId: %s, title: %s, completed: %s", id, userId, title, completed);
	}
}
  • 服务类

TodoService.java:

import java.util.List;

import feign.Param;
import feign.RequestLine;

public interface TodoService {

	@RequestLine("GET")
	List<Todo> findAll();
	
	@RequestLine("GET /{id}")
	Todo findOne(@Param("id") int id);
}
  • 主程序
public class App {

	public static void main(String[] args) {
		org.slf4j.Logger log = LoggerFactory.getLogger(App.class);
		TodoService client = Feign.builder()
				.client(new OkHttpClient())
				.encoder(new GsonEncoder())
				.decoder(new GsonDecoder())
				.logger(new Slf4jLogger(App.class))
				.logLevel(Logger.Level.FULL)
				.target(TodoService.class, "https://jsonplaceholder.typicode.com/todos");
		List<Todo> all = client.findAll();
		if(all != null) {
			log.info("Size: " + all.size());
		}
		Todo todo = (Todo) client.findOne(1);
		log.info(String.valueOf(todo));
	}
}
  • 运行结果

输出:

[main] INFO  demo.App - Size: 200
[main] INFO  demo.App - id: 1, userId: 1, title: delectus aut autem, completed: false

由于样例中日志设置了DEBUG级别,在输出内容中可以看到详细的请求过程和返回结果。

三、Spring Boot样例

  • 数据源

同上面样例中的数据源

  • 模型类

同上面样例中的模型类

  • 依赖

pom.xml:

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.7.18</version>
	<relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-dependencies</artifactId>
			<version>2021.0.9</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>	
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-openfeign</artifactId>
	</dependency>	
</dependencies>
  • 服务类
import java.util.List;

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.RequestMethod;

@FeignClient(name = "todo-service", url = "https://jsonplaceholder.typicode.com")
public interface TodoService {

	@RequestMapping(value = "/todos", method = RequestMethod.GET)
	List<Todo> findAll();
	
	@RequestMapping(value = "/todos/{id}", method = RequestMethod.GET)
	Todo findOne(@PathVariable("id") int id);
}
  • 控制器类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TodoController {

	@Autowired
	private TodoService todoService;
	
	@GetMapping("/getTodos")
	public ResponseEntity<?> getTodos(){
		return ResponseEntity.ok(todoService.findAll());
	}
	
	@GetMapping("/getTodo/{id}")
	public ResponseEntity<?> getTodo(@PathVariable int id){
		return ResponseEntity.ok(todoService.findOne(id));	
	}
}
  • 主程序
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class App {

	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
}
  • 运行

访问http://localhost:8080/getTodos可以获取所有待办事项;

访问http://localhost:8080/getTodo/1可以获取id为1的待办事项。

参考资料