什么是 OpenFeign
OpenFeign (以下统一简称为 Feign) 是 Netflix 开源的声明式 HTTP 客户端,集成了 Ribbon 的负载均衡、轮询算法和 RestTemplate 的 HTTP 调用等特性,并对其进行封装,使用者只需要在此基础上,定义一个接口,并在接口上标注一个 FeignClient
,便可以实现 HTTP 远程调用,上面的 声明式 HTTP 如何理解,可以理解为
只需要声明一个接口,Feign 就会通过你定义的接口,自动给你构造请求的目标地址并请求。
下面介绍下如何在项目中集成 Feign 组件,只需要遵循 SpringBoot 开发三板斧(1、加依赖,2、加注解,3、加配置)即可
环境准备
-
加依赖
openfeign
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
-
加注解
@EnableFeignClients
@SpringBootApplication @EnableFeignClients public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
-
加配置(由于 feign 不需要额外在 application.yml 或者 application.properties)中配置,只需要配置好调用微服务的名称和端口即可,下面举个例子,user-center 就是我们调用的微服务
server: port: 8081 spring: cloud: nacos: discovery: server-addr: localhost:8848 # 微服务名称 application: name: user
新建 OpenFeign 接口
@FeignClient(name = "user") public interface UserFeignClient { @GetMapping("/users/{id}") UserDto findById(@PathVariable Integer id); }
新建 Controller 层
@Service public class ArticleController { @Resource private UserFeignClient userFeignClient; public ArticleDto findByUserId(Integer userId) { ... ... ArticleDto articleDto = this.userFeignClient.findById(userId); ... ... return articleDto; } }
以上,就是 openFeign 的基本使用入门了
Feign的日志配置
由于 Feign 在没有配置的情况下是不会打印任何日志,如果想要看到 Feign 的日志,需要额外的配置;但是在此之前,我们先了解下 Feign 的自定义日志级别。
Feign 的自定义日志级别
级别 | 打印内容 |
---|---|
NONE(默认值) | 不记录任何日志 |
BASIC | 仅记录请求方法、URL、响应状态码以及执行时间 |
HEADERS | 记录 BASIC 级别的基础上,记录请求和响应的 header |
FULL | 记录请求和响应的 header、body 和元数据 |
Feign 的自定义日志级别可以通过 Java 代码方式或配置属性方式来实现,下面我们先介绍下代码的实现方式
代码配置方式
-
编写一个 Configuration 的类
public class UserFeignConfiguration { @Bean public Logger.Level level() { // 生产上不建议使用FULL,这样会产生大量的日志,影响性能的同时还不好定位问题。 // 建议使用 BASIC return Logger.Level.FULL; } }
-
然后在对应的 Feign 接口设置 Configuration 类
@FeignClient(name = "user", configuration = UserFeignConfiguration.class) public interface UserFeignClient { @GetMapping("/users/{id}") UserDto findById(@PathVariable Integer id); }
-
最后还要将 Feign接口类的全路径设置在 yml 文件里面
logging: level: # Feign接口类的全路径 # Feign的日志级别是建立在Feign的接口 debug 级别之上的 com.example.article.feignclient.UserFeignClient: debug
当调用接口时,输出日志结果如下
[UserFeignClient#findById] <--- HTTP/1.1 200 (794ms) [UserFeignClient#findById] content-type: application/json;charset=UTF-8 [UserFeignClient#findById] date: Mon, 29 Aug 2022 10:56:27 GMT [UserFeignClient#findById] transfer-encoding: chunked [UserFeignClient#findById] [UserFeignClient#findById] {"id":1,"username":"张三","createTime":"2022-08-25T17:17:04.000+0000","updateTime":"2022-08-25T17:17:04.000+0000"} [UserFeignClient#findById] <--- END HTTP (180-byte body)
这样,通过代码来设置 Feign 的日志自定义级别方式就配置好了。但是这样,并不是完美的解决方案,就相当于每次新建一个 Feign 接口,就要编写一个对应的 xxxFeignConfiguration 类,然后在 Feign 接口上指定,这样下来太繁琐了,下面看下全局的代码配置方式:
不需要在 每个 Feign 接口上面都要配置下
configuration = UserFeignConfiguration.class
,我们只需要在 Application 启动类上面,指定@EnableFeignClients(defaultConfiguration = GlobalFeignConfiguration.class)
,注:这里将 UserFeignConfiguration 已经更名为 GlobalFeignConfiguration@SpringBootApplication @EnableFeignClients(defaultConfiguration = GlobalFeignConfiguration.class) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
属性配置方式
在 application.yml 加入只需要配置以下的属性既可以实现 Feign 的自定义级别
feign: client: config: # 微服务名称 user: loggerLevel: full
同理,yml 文件也支持 全局的属性配置方式
feign: client: config: # 全局配置(default 默认就是适用于全部微服务) default: loggerLevel: full
Feign的多参数请求构造
GET 请求
请求多参数的URL,如请求地址为 http://www.xxx.me/admin/user/get?username=张三&school=阳光小学&birthDay=2012-08-01
,SpringCloud 为 Feign 整合了 SpringMVC 的注解支持
-
@SpringQueryMap
注解@FeignClient("user") public interface UserFeignClient { @RequestMapping(value = "/get", method = RequestMethod.GET) public User get(@SpringQueryMap User user); }
-
@RequestParam
注解(表单传参)@FeignClient("user") public interface UserFeignClient { @RequestMapping(value = "/get", method = RequestMethod.GET) public User get(@RequestParam("username") String username, @RequestParam("school") String school, @RequestParam("birthDayDay") String birthDay); }
-
@PathVariable
注解(URL携带参数)@FeignClient("user") public interface UserFeignClient { @RequestMapping(value = "/get", method = RequestMethod.GET) public User get(@PathVariable("username") String username, @PathVariable("school") String school, @PathVariable("birthDayDay") String birthDay); }
POST 请求
Feign 默认的传参方式就是 JSON 传参( @RequestBody
),因此定义接口的时候可以不用 @RequestBody
注解标注,但为了开发规范,建议加上
-
@RequestBody
注解@FeignClient("user") public interface UserFeignClient { @RequestMapping(value = "/post", method = RequestMethod.POST) public User post(@RequestBody User user); }
超时设置
我们在通过 Feign 去调用接口,难免会遇到超时的问题,我们可以在 yml 文件设置超时属性,防止系统抛出超时异常
feign: client: config: # 全局配置(default 默认就是适用于全部微服务) default: connectTimeout: 100000 readTimeout: 100000 # 单独配置 user: connectTimeout: 300000 readTimeout: 300000
Feign 性能优化
默认情况下,Feign 使用的是 UrlConnetcion 去请求,这种原生的请求方式一旦遇到高并发的情况下,响应会变得很慢,所以我们可以考虑加入连接池技术来优化性能,下面介绍下如何集成 Apache 下的 HttpClient 的连接池
-
加入 httpclient 依赖
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>
-
设置 yml 文件属性
feign: # 这样就设置好了 feign 请求方式是 httpclient,而不是 UrlConnetcion httpclient: enable: true # feign的最大连接数 max-connection: 200 # feign 单个路径的最大连接数 max-connections-per-route: 50