微服务|【云原生&微服务十】SpringCloud之OpenFeign实现服务间请求头数据传递(OpenFeign拦截器RequestInterceptor的使用)
一、前言 在前面的文章:
SpringCloud之Feign实现声明式客户端负载均衡详细案例我们聊了OpenFeign的概述、为什么会使用Feign代替Ribbon、Feign和OpenFeign的区别、以及详细的OpenFeign实现声明式客户端负载均衡案例。
在一些业务场景中,微服务间相互调用需要做鉴权,以保证我们服务的安全性。即:服务A调用服务B的时候需要将服务B的一些鉴权信息传递给服务B,从而保证服务B的调用也可以通过鉴权,进而保证整个服务调用链的安全。
本文我们就讨论如果通过openfeign的拦截器
RequestInterceptor
实现服务调用链中上下游服务请求头数据的传递。二、实现RequestInterceptor 通过RequestInterceptor 拦截器拦截我们的openfeign服务请求,将上游服务的请求头或者请求体中的数据封装到我们的openfeign调用的请求模板中,从而实现上游数据的传递。
1、RequestInterceptor实现类 1)RequestInterceptor实现类
package com.saint.feign.config;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.Objects;
/**
* 自定义的Feign拦截器
*
* @author Saint
*/
@Slf4j
public class MyFeignRequestInterceptor implements RequestInterceptor {
/**
* 这里可以实现对请求的拦截,对请求添加一些额外信息之类的
*
* @param requestTemplate
*/
@Override
public void apply(RequestTemplate requestTemplate) {
// 1. obtain request
final ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
// 2. 兼容hystrix限流后,获取不到ServletRequestAttributes的问题(使拦截器直接失效)
if (Objects.isNull(attributes)) {
log.error("MyFeignRequestInterceptor is invalid!");
return;
}
HttpServletRequest request = attributes.getRequest();
// 2. obtain request headers,and put it into openFeign RequestTemplate
Enumeration headerNames = request.getHeaderNames();
if (Objects.nonNull(headerNames)) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String value = https://www.it610.com/article/request.getHeader(name);
requestTemplate.header(name, value);
}
}// todo 需要传递请求参数时放开
// 3. obtain request body, and put it into openFeign RequestTemplate
//Enumeration bodyNames = request.getParameterNames();
//StringBuffer body = new StringBuffer();
//if (bodyNames != null) {
//while (bodyNames.hasMoreElements()) {
//String name = bodyNames.nextElement();
//String value = request.getParameter(name);
//body.append(name).append("=").append(value).append("&");
//}
//}
//if (body.length() != 0) {
//body.deleteCharAt(body.length() - 1);
//requestTemplate.body(body.toString());
//log.info("openfeign interceptor body:{}", body.toString());
//}
}
}
2)使RequestInterceptor生效(均已验证)
使RequestInterceptor生效的方式有四种;
1> 代码方式全局生效 直接在Spring可以扫描到的路径使用@Bean方法将
RequestInterceptor实现类
注入到Spring容器;package com.saint.feign.config;
import feign.RequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Saint
*/
@Configuration
public class MyConfiguration {@Bean
public RequestInterceptor requestInterceptor() {
return new MyFeignRequestInterceptor();
}
}
2> 配置方式全局生效
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
# 拦截器配置(和@Bean的方式二选一)
requestInterceptors:
- com.saint.feign.config.MyFeignRequestInterceptor
3> 代码方式针对某个服务生效 直接在@FeignClient注解中指定configuration属性为
RequestInterceptor实现类
;文章图片
4、配置方式针对某个服务生效
feign:
client:
config:
SERVICE-A:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
# 拦截器配置(和@Bean的方式二选一)
requestInterceptors:
- com.saint.feign.config.MyFeignRequestInterceptor
2、效果验证 1)
feign-server
服务改造在文章 SpringCloud之Feign实现声明式客户端负载均衡详细案例的基础下,我们修改
feign-server
项目,添加一个MVC拦截器(用于获取请求头中的数据)文章图片
1> MvcInterceptor
package com.saint.feign.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义MVC拦截器
*
* @author Saint
*/
@Slf4j
public class MvcInterceptor implements HandlerInterceptor {@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("token-saint");
log.info("obtain token is : {}", token);
return true;
}}
2> MvcInterceptorConfig 设置MVC拦截器会拦截哪些路径的请求,这里是所有的请求全部拦截。
package com.saint.feign.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* MVC拦截器配置
*
* @author Saint
*/
@Configuration
public class MvcInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MvcInterceptor())
.addPathPatterns("/**");
}
}
2)结果验证
1> 执行请求:
文章图片
文章图片
2> feign-consumer中的日志:
文章图片
3> feign-server中的日志:
文章图片
结果显示,RequestInterceptor生效了
三、结合Hystrix限流使用时的坑(仅做记录) 此处OpenFeign依赖的SpringCloud版本是2020.X之前。
【微服务|【云原生&微服务十】SpringCloud之OpenFeign实现服务间请求头数据传递(OpenFeign拦截器RequestInterceptor的使用)】在application.yaml文件中做如下配置开启了Hystrix限流:
feign:
hystrix:
enabled: true
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 30000
做完上述配置后,Feign接口的熔断机制为:线程模式;
如果我们自定义了一个
RequestInterceptor实现类
,就会导致hystrix熔断机制失效,接口调用异常(404、null);1、原因分析
- 在feign调用之前,会走RequestInterceptor拦截器,拦截器中使用了
ServletRequestAttributes
获取请求数据; - 默认feign使用的是线程池模式,当开启熔断的时候,负责熔断的线程和执行Feign接口的线程不是同一个线程,ServletRequestAttributes取到的将会是空值。
feign:
hystrix:
enabled: true
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 30000
strategy: SEMAPHORE
3、Hystrix线程和信号量隔离区别
文章图片
4、线程和信号量隔离的使用场景?
1> 线程池隔离
- 请求并发量大,并且耗时长(一般是计算量大或者读数据库);
- 采用线程池隔离,可以保证大量的容器线程可用,不会由于其他服务原因,一直处于阻塞或者等待状态,快速失败返回。
2> 信号量隔离- 请求并发量大,并且耗时短(一般是计算量小,或读缓存);
- 采用信号量隔离时的服务的返回往往非常快,不会占用容器线程太长时间;
- 其减少了线程切换的一些开销,提高了缓存服务的效率 。
推荐阅读
- SpringCloud|SpringCloud Alibaba【微服务组件Feign&Nacos配置中心使用】
- vue.js|Vue基础知识总结 5(vue实现树形结构)
- mall学习教程|阿里出品,SpringBoot应用自动化部署神器,IDEA版Jenkins()
- Spring|17. Spring Boot整合Thymeleaf
- Spring|【编程不良人】快速入门SpringBoot学习笔记05---RestFul、异常处理、CORS跨域、Jasypt加密
- Spring|Spring Boot技术知识点(如何实现二维码生成工具)
- SpringBoot总结|SpringBoot(拦截器、Servlet、过滤器(动力))
- Java|Java Springboot项?部署到linux任意文件夹或tomcat,并使用nginx实现域名访问
- spring|40个SpringBoot常用注解(让生产力爆表!)