Skip to content

一、OpenFeign简介

什么是OpenFeign

OpenFeign目前是Spring Cloud 二级子项目。平时说的Feign指的是Netflix下的Feign,现在我们学习的是OpenFeign,是Spring提供的。

OpenFeign是一种声明式、模板化的HTTP客户端(仅在Application Client中使用)。

OpenFeign的作用:声明式的服务调用。声明式调用是指,就像调用本地方法一样调用远程方法,无需感知操作远程http请求。学习完OpenFeign后可以不使用RestTemplate进行调用。

使用OpenFeign时就好像在写控制器方法,OpenFeign都是写在接口中,在声明的方法上添加SpringMVC注解或声明的参数上添加SpringMVC注解就可以完成调用远程的控制器方法。

OpenFeign的执行流程

文字说明

整体流程说明:

  1. Application ServiceEureka Server 注册服务。
  2. Application ClientEureka Server中发现服务。
  3. Application Client中调用OpenFeign接口中方法。
  4. Application Client中OpenFeign通过应用程序名访问Application Service
  5. OpenFeign访问远程服务时,基于Ribbon实现负载均衡。

二、第一个OpenFeign项目

Application Service应用准备

创建Application service项目

创建项目application_service

POM依赖

xml
<parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.3.12.RELEASE</version>
</parent>
<dependencyManagement>
 <dependencies>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-dependencies</artifactId>
 <version>Hoxton.SR12</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-netflix-eureka-client</artifactId>
 </dependency>
</dependencies>

编写配置文件

编写配置文件application.yml

yaml
server:
 port: 8888
spring:
 application:
 name: application-service

编写controller方法

java
package com.lhp.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * `@author` lhp
 * `@create` 2025-08-14 21:17
 */
`@RestController`
public class UserController {

 `@GetMapping`("/test1")
 public String test1(){
 return "我是test1方法被执行返回的内容!";
 }

 `@PostMapping`("/test2")
 public String test2(){
 return "我是test2方法被执行返回的内容!";
 }
}

编写启动类

编写启动类,然后启动项目。

Application Client应用开发

创建Application Client项目

创建项目application_client_openfeign

POM依赖

xml
<parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.3.12.RELEASE</version>
</parent>
<dependencyManagement>
 <dependencies>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-dependencies</artifactId>
 <version>Hoxton.SR12</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-netflix-eureka-client</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-openfeign</artifactId>
 </dependency>
</dependencies>

编写配置文件

编写配置文件application.yml

yaml
server:
 port: 9999
spring:
 application:
 name: eureka-client-open-feign

编写OpenFeign本地接口

编写接口 com.lhp.feign.AppServiceOpenfeignClient

java
/**
 * 定义接口,基于注解,实现声明式远程服务调用。
 * 技术是OpenFeign。
 * 需要确定的事情:
 * 1. 访问的远程服务名称是什么。
 * 2. 访问的远程服务具体地址是什么。
 * 3. 访问的远程服务请求方式是什么。
 * 4. 访问的远程服务,参数是什么。
 * 5. 访问的远程服务,返回结果类型是什么。
 *
 * FeignClient - 代表当前的接口是一个OpenFeign客户端,要访问远程的服务。
 * 具体的实现类对象,由spring cloud动态生成代理对象来实现。
 * 必要属性: value - 要访问的远程服务命名是什么。
 */
`@FeignClient`("application-service")
public interface UserFeignClient {
 /**
 * 定义方法。使用SpringMVC注解+方法定义,实现远程服务访问规则定义。
 * 建议写法: 找到要访问的控制器。复制对应的方法签名即可。
 *
 * GetMapping - 约束了请求方式
 * 注解属性value - 约束了请求的具体地址
 * 方法返回值 - 约束了远程服务返回结果类型
 * 方法参数表 - 约束了远程服务的请求参数
 */
 `@GetMapping`("/test1")
 public String test1();

 /**
 * post请求,无参数
 * `@return`
 */
 `@PostMapping`("/test2")
 public String test2();
}

编写服务接口即实现

也就是编写application client端的service层代码,编写service层的接口和实现类,在实现类中通过OpenFeign调用远程的接口。

编写服务接口

编写接口com.lhp.serivce.OpenfeignService

java
/**
 * 本地服务接口。
 */
public interface OpenfeignService {
 String test1();

 String test2();
}

编写服务实现

编写实现com.lhp.serivce.impl.OpenfeignServiceImpl

java
/**
 * 本地服务实现类
 */
`@Service`
public class OpenFeignServiceImpl implements OpenFeignService {

 `@Autowired`
 private UserFeignClient feignClient;

 `@Override`
 public String test1() {
 return feignClient.test1();
 }

 `@Override`
 public String test2() {
 return feignClient.test2();
 }
}

编写控制器

编写控制器com.lhp.controller.OpenfeignController

java
`@RestController`
public class OpenFeignController {

 `@Autowired`
 private OpenFeignService openFeignService;

 `@GetMapping`("/test1")
 public String test1(){
 return openFeignService.test1();
 }

 `@PostMapping`("/test2")
 public String test2(){
 return openFeignService.test2();
 }
}

编写启动类型

java
/**
 * EnableFeignClients - 开启Openfeign技术。让spring cloud扫描Openfeign相关注解,
 * 生成动态代理实现对象。
 * 可选属性 basePackages = {"feign接口所在包1", "feign接口所在包2"}
 * 默认扫描当前类型所在包,及所有子孙包。
 */
`@SpringBootApplication`
`@EnableFeignClients`(basePackages = {"com.lhp.feign"})
public class OpenFeignAppClientApp {
 public static void main(String[] args) {
 SpringApplication.run(OpenFeignAppClientApp.class, args);
 }
}

测试

启动application_client_openfeign项目,访问 http://localhost:8888/test1 和 http://localhost:8888/test2。观察结果。

总结

核心实现原理

OpenFeign通过为@FeignClient注解的接口生成动态代理对象,在调用时根据注解信息构造请求地址,最终通过HTTP客户端(如JDK的HttpURLConnection或第三方库HttpClient/OkHttp)发起远程调用。这种设计使其能像调用本地方法一样进行服务间通信。

RestTemplate的对比

  • 调用方式‌:RestTemplate需要手动拼接URL和参数,而OpenFeign通过接口声明自动完成请求封装。
  • 负载均衡‌:两者均可整合Ribbon实现负载均衡,但OpenFeign的负载均衡通过动态代理自动处理,RestTemplate需显式添加@LoadBalanced注解。
  • 底层通信‌:RestTemplate直接使用HTTP客户端,而OpenFeign在代理层封装了HTTP调用细节。

技术演进

OpenFeign最初由Netflix开发,后捐赠给Spring Cloud社区并整合了Ribbon等组件,其设计目标是简化声明式REST调用。虽然早期版本可能依赖RestTemplate,但现代实现已独立于具体HTTP客户端。

总结来说,OpenFeign和RestTemplate是两种独立的服务调用方案,前者通过动态代理提供更高级的抽象,后者则是更基础的HTTP工具类。

三、OpenFeign参数管理

传递简单类型参数

GET请求传递参数

修改OpenFeign接口,增加方法

java
/**
 * 传递参数。
 * 远程服务无论参数类型是什么(简单参数,自定义实体类型),都是模拟浏览器,传递多个简单参数。
 *
 * openfeign传递参数的时候,方法参数表要固定要求:
 * 1. 只能有唯一一个参数不写任何注解。
 * 2. 传递参数的时候,都要提供`@RequestParam注解`,代表这个方法参数,是一个表单或请求头参数。
 * 注解的属性value,必须定义,代表具体的请求参数名称是什么。
 * 3. 如果是Restful参数,必须提供`@PathVariable注解`,代表这个方法参数是一个restful参数。
 * 且PathVariable注解的属性value,必须定义,对应路径地址中的占位变量名。
 */
`@GetMapping`("/getWithParams")
public String getWithParams(`@RequestParam`("name") String name, `@RequestParam`("age") int age);

修改服务接口和实现,增加方法

java
String getWithParams(String name, int age);
java
/**
 * get请求,有参数
 * `@param` name
 * `@param` age
 * `@return`
 */
`@Override`
public String getWithParams(String name, int age) {
 String result = appServiceOpenfeignClient.getWithParams(name, age);
 System.out.println("远程返回:" + result);
 return result;
}

修改控制器,增加方法

java
`@RequestMapping`("/getWithParams")
public String getWithParams(String name, int age){
 System.out.println("控制器执行 - getWithParams()");
 return openfeignService.getWithParams(name, age);
}

POST请求传递参数

修改OpenFeign接口,增加方法

java
/**
 * post请求,有参数
 * `@return`
 */
`@PostMapping`("/postWithParams")
public String postWithParams(`@RequestParam`("name") String name, `@RequestParam`("age") int age);

修改服务接口和实现,增加方法

java
String postWithParams(String name, int age);
java
`@Override`
public String postWithParams(String name, int age) {
 String result = appServiceOpenfeignClient.postWithParams(name, age);
 System.out.println("远程返回: " + result);
 return result;
}

修改控制器,增加方法

java
`@RequestMapping`("/postWithParams")
public String postWithParams(String name, int age){
 System.out.println("控制器执行 - postWithParams()");
 return openfeignService.postWithParams(name, age);
}

请求体传递参数

修改OpenFeign接口,增加方法

java
/**
 * RequestBody参数 - 一个方法上只能定义唯一一个没有任何注解修饰的方法参数。就是RequestBody参数。
 * 建议还是使用`@RequestBody注解修饰指定的方法参数`。明确这个参数是如何传递的。
 * 一个方法,可以在传递RequestBody参数的同时,传递其他参数。
 * `@return`
 */
`@PostMapping`("/postBodyParams")
public String postBodyParams(`@RequestBody` Map<String, String> params, `@RequestParam`("name") String name);

修改服务接口和实现,增加方法

java
String postBodyParams(Map<String, String> params, String name);
java
`@Override`
public String postBodyParams(Map<String, String> params, String name) {
 String result = appServiceOpenfeignClient.postBodyParams(params, name);
 System.out.println("远程返回:" + result);
 return result;
}

修改控制器,增加方法

java
`@RequestMapping`("/postBodyParams")
public String postBodyParams(String name){
 // 把请求参数封装处理,并调用服务方法
 Map<String, String> params = new HashMap<>();
 params.put("name", name);

 System.out.println("控制器执行 - postBodyParams()");

 return openfeignService.postBodyParams(params, name);
}

restful参数

修改OpenFeign接口,增加方法

java
/**
 * restful参数。要求注解必须提供,属性value必须定义。一般我们开发的时候都会省略value属性的设置。
 * 原因参考1.2.2章节。
 * `@param` name
 * `@param` age
 * `@return`
 */
`@RequestMapping`("/restfulParams/{name}/{age}")
public String restfulParams(`@PathVariable`("name") String name,
 `@PathVariable`("age") int age);

restful参数注意事项

在spring-cloud-starter-openfeign中,对PathVariable有额外的约束。

要求必须为PathVariable注解的属性value赋值,如果未赋值属性value,则抛出异常:

java.lang.IllegalStateException: PathVariable annotation was empty on param 0.具体如下:

源码中的约束:

一般情况下,此异常不会出现。但并不代表此约束不存在,我们开发的时候,如果工程是继承spring-boot-starter-parent,则不会出现此问题,因为在spring-boot-starter-parent中,定义了注解参数自动补偿插件,插件是:

xml
<plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-compiler-plugin</artifactId>
 <configuration>
 <parameters>true</parameters>
 </configuration>
</plugin>

如果工程不是继承spring-boot-starter-parent,则可以手工管理插件。

修改服务接口和实现,增加方法

java
String restfulParam(String name, int age);
java
`@Override`
public String restfulParam(String name, int age) {
 String result = appServiceOpenfeignClient.restfulParams(name, age);
 System.out.println("远程返回:" + result);
 return result;
}

修改控制器,增加方法

java
`@RequestMapping`("/restfulParam/{name}/{age}")
public String restfulParam(`@PathVariable` String name, `@PathVariable`("age") int age){
 System.out.println("控制器执行 - restfulParam()");
 return openfeignService.restfulParam(name, age);
}

四、OpenFeign通讯优化

GZIP简介

gzip介绍:gzip是一种数据格式,采用用deflate算法压缩数据;gzip是一种流行的数据压缩算法,应用十分广泛,尤其是在Linux平台。

gzip能力:当Gzip压缩到一个纯文本数据时,效果是非常明显的,大约可以减少70%以上的数据大小。

gzip作用:网络数据经过压缩后实际上降低了网络传输的字节数,最明显的好处就是可以加快网页加载的速度。网页加载速度加快的好处不言而喻,除了节省流量,改善用户的浏览体验外,另一个潜在的好处是Gzip与搜索引擎的抓取工具有着更好的关系。例如 Google就可以通过直接读取gzip文件来比普通手工抓取更快地检索网页。

HTTP协议中关于压缩传输的规定(原理)

第一:客户端向服务器请求头中带有:Accept-Encoding:gzip, deflate 字段,向服务器表示,客户端支持的压缩格式(gzip或者deflate),如果不发送该消息头,服务器是不会压缩的。

第二:服务端在收到请求之后,如果发现请求头中含有Accept-Encoding字段,并且支持该类型的压缩,就对响应报文压缩之后返回给客户端,并且携带Content-Encoding:gzip消息头,表示响应报文是根据该格式压缩过的。

第三:客户端接收到响应之后,先判断是否有Content-Encoding消息头,如果有,按该格式解压报文。否则按正常报文处理。

在OpenFeign技术中应用GZIP压缩

在Spring Cloud微服务体系中,一次请求的完整流程如下:

在整体流程中,如果使用GZIP压缩来传输数据,涉及到两次请求-应答。而这两次请求-应答的连接点是Application Client,那么我们需要在Application Client中配置开启GZIP压缩,来实现压缩数据传输。

配置OpenFeign请求-应答的GZIP压缩

在交互数据量级不够的时候,看不到压缩内容。

这里只开启Feign请求-应答过程中的GZIP,也就是浏览器-Application Client之间的请求应答不开启GZIP压缩。

在全局配置文件中,使用下述配置来实现OpenFeign请求-应答的GZIP压缩(在使用Feign的微服务配置即可)

yaml
# 配置openfeign请求和应答的gzip压缩处理
feign:
 compression:
 request:
 enabled: true # 开启请求压缩处理。默认false
 min-request-size: 128 # 请求容量多少,开始压缩。默认2048字节
 mime-types: text/html, text/xml, text/plain, text/css, application/json # 请求头content type是什么,做压缩处理
 response:
 enabled: true # 开启响应压缩处理。默认false

Tomcat服务器GZIP优化配置

在配置文件中提供下述配置(在浏览器访问的那个微服务中配置即可):

yaml
server:
 compression:
 enabled: true # 是否开启响应压缩处理。默认false
 mime-types: text/html, text/xml, text/plain, text/css, text/javascript, application/javascript, application/json, application/xml # 响应content type什么类型,做压缩处理。
 min-response-size: 128 # 响应容量多大,做压缩处理。 默认2048字节

测试

最终我们可以在浏览器中观察:

在配置前的响应头中是没有 content-encoding** **的

在配置后的响应头中是有 content-encoding 的