005-声明式HTTP客户端-Feign
# 一、介绍
Feign 使编写 java http 客户端更容易
Feign 是受Retrofit (opens new window)、JAXRS-2.0 (opens new window)和WebSocket (opens new window)启发的 Java 到 HTTP 客户端绑定器。Feign 的第一个目标是降低将Denominator (opens new window)统一绑定到 HTTP API的复杂性,而不管ReSTfulness (opens new window)。
Spring Cloud OpenFeign (opens new window)
Spring Cloud OpenFeign 中文文档 (opens new window)
# 二、使用Feign
# 2.1 添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2
3
4
# 2.2 添加注解,启用Feign
@EnableFeignClients
# 2.3 创建服务调用接口
@FeignClient(name = "user-center") //服务名
public interface UserCenterFeignClient {
@GetMapping("/users/{id}")
UserDTO findById(@PathVariable Integer id);
}
2
3
4
5
# 三、Feign的组成
接口 | 作用 | 默认值 |
---|---|---|
Feign.Builder | Feign的入口 | Feign.Builder |
Client | Feign底层用什么去请求 | 和Ribbon配合时: LoadBalancerFeignClient 不和Ribbon配合时:feign.Client.Default |
Contract | 契约,注解支持 | SpringMvcContract |
Encoder | 编码器,用于将对象转换成HTTP请求消息体 | SpringEncoder |
Decoder | 解码器,将响应消息体转换成对象 | ResponseEntityDecoder |
Logger | 日志管理器 | Slf4jLogger |
RequestInterceptor | 用于为每个请求添加通用逻辑 | 无 |
# 四、Feign的日志
为每个创建的 FeignClient 端创建一个 Logger。默认情况下,Logger 的名称是用于创建 FeignClient 端的接口的全类名称。Feign日志仅响应DEBUG
级别。
# 4.1 日志级别
级别 | 打印内容 |
---|---|
NONE(默认) | 不记录任何日志 |
BASIC | 仅记录请求方法、URL、响应状态代码以及执行时间 |
HEADERS | 记录BASIC级别的基础上,记录请求和响应的header |
FULL | 记录请求和响应的header、body和元数据 |
# 4.2 Feign细粒度配置
# 4.2.1 Java代码方式
- 配置在@FeignClient中有个属性
configuration
可以指定Feign的配置
@FeignClient(name = "user-center", configuration = UserCenterFeignConfiguration.class)
public interface UserCenterFeignClient {
@GetMapping("/users/{id}")
UserDTO findById(@PathVariable Integer id);
}
2
3
4
5
6
7
- UserCenterFeignConfiguration.java
/**
* feign的配置类
* 这个类别加@Configuration注解了,否则必须挪到@ComponentScan能扫描的包以外
*/
public class UserCenterFeignConfiguration {
@Bean
public Logger.Level level(){
//让feign打印所有请求的细节。
return Logger.Level.FULL;
}
}
2
3
4
5
6
7
8
9
10
11
- application.yml
logging:
level:
top.banner.contentcenter.feignclient.UserCenterFeignClient: debug
2
3
🤯 Feign的日志级别是建立在Feign的接口日志为DEBUG
级别的前提下的,如果接口的日志不为DEBUG
,则什么日志都不打印。
# 4.2.2 配置的方式
application.yml
logging:
level:
top.banner.contentcenter.feignclient.UserCenterFeignClient: debug
feign:
client:
config:
# 想要调用的微服名称。
user-center:
loggerLevel: full
2
3
4
5
6
7
8
9
# 4.3 Feign全局配置
# 4.3.1 Java代码方式
- 修改@EnableFeignClients
@EnableFeignClients(defaultConfiguration = GlobalFeignConfiguration.class)
- GlobalFeignConfiguration.java
/**
* 全局feign的配置类
* 这个类别加@Configuration注解了,否则必须挪到@ComponentScan能扫描的包以外
*/
public class GlobalFeignConfiguration {
@Bean
public Logger.Level level(){
//让feign打印所有请求的细节。
return Logger.Level.FULL;
}
}
2
3
4
5
6
7
8
9
10
11
# 4.3.2 配置的方式
logging:
level:
top.banner.contentcenter.feignclient.UserCenterFeignClient: debug
feign:
client:
config:
# 全局配置。
default:
loggerLevel: full
2
3
4
5
6
7
8
9
# 五、Feign支持的配置项
# 5.1 Java代码支持的配置项
配置项 | 作用 |
---|---|
Logger.Level | 指定日志级别 |
Retryer | 指定重试策略 |
ErrorDecoder | 指定错误解码器 |
Request.Options | 超时时间 |
Collection<RequestInterceptor> | 拦截器 |
SetterFactory | 用户设置Hystrix的配置属性,Feign整合Hystrix才会用 |
🤯:配置方式参考 [Feign细粒度配置](#4.2 Feign细粒度配置)
# 5.2 配置方式支持的配置项
feign.client.config:
<feignName>:
connectTimeout: 5000 #连接超时时间
readTimeout: 5000 #读取超时时间
loggerLevel: full #日志级别
errorDecoder: com.example.SimpleErrorDecoder #错误解码器
retryer: com.example.SimpleRetryer # 重试策略
requestInterceptors:
- com.example.FooRequestInterceptor # 拦截器
# 是否对404错误码解码
# 处理逻辑详见feign.SynchronousMethodHandler #executeAndDecode
decode404: false
encoder: com.example.SimpleEncoder #解码器
decoder: com.example.SimpleDecoder #解码器
contract: com.example.SimpleContract #契约
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 六、Feign的继承
通常不建议在服务器和客户端之间共享一个接口。它引入了紧耦合,实际上也不适用于当前形式的 Spring MVC(方法参数 Map 不被继承)。
# 七、构造多参数请求
# GET 请求 /q?id=1&username=张三
- @SpringQueryMap User user (推荐)
- @RequestParam("id") Long id , @RequestParam("username") String username (推荐)
- @RequestParam Map<String,Object> map (不推荐)
# Post 请求
@PostMapping("/post")
public User post(@RequestBody User user) {
...
}
2
3
4
@RequestBody User user
1
# 八、Feign脱离Ribbon使用
之前的Feign
调用都是通过Ribbon
调用注册在Nacos
上的实例的接口,有时需要脱离Ribbon
直接调用没注册在Nacos
上的服务下的接口。比如,直接调用百度的网址。
@FeignClient(name = "baidu", url = "http://www.baidu.com")
public interface TestBaiduFeignClient {
@GetMapping
String index();
}
2
3
4
5
需要注意的是,虽然通过FeignClient
注解里的url
属性添加了百度的地址,但是name
属性依然必须要有,否则会导致项目无法启动。
# 九、Feign性能优化
Feign
的性能比RestTemplate
的性能要差很多,主要有两个原因:
- 默认使用UrlConnection,而不是连接池
- 日志级别过高导致的性能损失
# 9.1 设置连接池
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
2
3
4
application.yml
feign:
httpclient:
# 让feign使用apache httpclient做请求;而不是默认的urlconnection
enabled: true
# feign的最大连接数
max-connections: 200
# feign单个路径的最大连接数
max-connections-per-route: 50
2
3
4
5
6
7
8
# 9.2 设置Feign日志级别为NONE或BASIC
# 十、Feign实现Token传递
# 10.1 方式一:通过@RequestHandler传递
# 10.2 方式二:通过实现 RequestInterceptor 的拦截器传递
创建一个拦截器 实现 RequestInterceptor
/**
* Feign 统一 Token拦截器
*/
public class TokenRelayRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
// 获取token
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
final ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
assert attributes != null;
final HttpServletRequest request = attributes.getRequest();
final String token = request.getHeader("X-Token");
//将token传递
if (StringUtils.isNotBlank(token)) {
requestTemplate.header("X-Token", token);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
全局配置
feign:
client:
config:
# 全局配置。
default:
loggerLevel: full
requestInterceptors:
- top.banner.contentcenter.feignclient.interceptory.TokenRelayRequestInterceptor
2
3
4
5
6
7
8
9
- 一、介绍
- 二、使用Feign
- 2.1 添加依赖
- 2.2 添加注解,启用Feign
- 2.3 创建服务调用接口
- 三、Feign的组成
- 四、Feign的日志
- 4.1 日志级别
- 4.2 Feign细粒度配置
- 4.3 Feign全局配置
- 五、Feign支持的配置项
- 5.1 Java代码支持的配置项
- 5.2 配置方式支持的配置项
- 六、Feign的继承
- 七、构造多参数请求
- GET 请求 /q?id=1&username=张三
- Post 请求
- 八、Feign脱离Ribbon使用
- 九、Feign性能优化
- 9.1 设置连接池
- 9.2 设置Feign日志级别为NONE或BASIC
- 十、Feign实现Token传递
- 10.1 方式一:通过@RequestHandler传递
- 10.2 方式二:通过实现 RequestInterceptor 的拦截器传递