直接在微服务远程调用中获取请求头数据不能直接获取到.为什么? 看源码
默认情况下feign远程调用的时候不会传递请求头!
远程调用源码:
每一次远程请求的时候都创建了一个新的Request Template对象,在该对象中不包含之前的请求头数据
解决方案:
方案一:在feign接口上添加对应的形式参数即可
弊端:每一个接口想要获取参数都需要在接口方法上添加对应的形式参数.影响代码效率
方案二:使用OpenFeign中的拦截器(RequestInterceptor)来拦截请求,添加请求头。
方案一演示:
FeignClient接口:
@FeignClient(value = "service-cart",fallback = FeignClientFallback.class)//降级方法
public interface FeignClient {
public Result xxx(
@RequestParam(value = "xx" )Long xx ,
@RequestParam(value = "xx" )String xx);
}
Controller远程接口:
@RestController
@RequestMapping("/x/x/x")
public class Controller {
@GetMapping("/x/x/x")
public Result aaa(
@RequestParam(value = "xx" )Long xx ,
@RequestParam(value = "xx" )String xx
) {
return Result.ok() ;
}
}
//@RequestParam 通过传参的方法传递过去,并非从请求头上拿
FeignClient.aaa(xx,xx); //远程调用时候传参
方案二演示:
思路:在OpenFeign远程调用 定义一个拦截器类实现(RequestInterceptor)接口给RequestTemplate设置上请求头
核心源码:
// feign.SynchronousMethodHandler#executeAndDecode
Request targetRequest(RequestTemplate template) {
// 在构建目标请求对象的时候,遍历所有的拦截器,
// 然后调用了apply方法把RequestTemplate作为参数传递过去
for (RequestInterceptor interceptor : requestInterceptors) {
interceptor.apply(template);
}
return target.apply(template);
}
拦截器类如何获取Controller请求头数据呢?
思路1:通过Map实现
原理:feign的调用链使用的是同一个线程对象
思路2:通过ThreadLocal对象在一个线程中共享HttpServletRequest对象(使用JDK自带的ThreadLocal在一个线程中共享HttpServletRequest对象,原理就是在底层维护了一个Map。)
思路3: 使用spring提供的对象RequestContextHolder进行实现!
原理:底层通过RequestContextListener监听器实现
思路1实现:
获取Controller中请求头思路:
feign的调用链使用的是同一个线程对象
所以可以在Controller中定义一个Map集合,键就是当前线程对象,值就是HttpServletRequest对象
//定义一个Map,作用是在tController和FeignClientInterceptor(拦截器)中共享HttpServletRequest
public static final ConcurrentHashMap<Thread, HttpServletRequest> concurrentHashMap =new ConcurrentHashMap<>();
//给map中存request对象
@GetMapping("/xx")//required:传递过来的参数可以有可以无
public String addCart(HttpServletRequest request, ) {
concurrentHashMap.put(Thread.currentThread(),request);
}
拦截器类通过map获取请求头数据,设置给RequestTemplate
@Component
@Slf4j
public class FeignClientInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
log.info("FeignClientInterceptor..........");
HttpServletRequest httpServletRequest = CartController.concurrentHashMap.get(Thread.currentThread());
//给requestTemplate 添加上对应的请求头中的数据:aa,bbb
requestTemplate.header("userId",httpServletRequest.getHeader("aa"));
requestTemplate.header("userTempId",httpServletRequest.getHeader("bbb"));
}
}
思路2实现:
在Controller中定义一个ThreadLocal对象
public static final ThreadLocal<HttpServletRequest> threadLocal = new ThreadLocal<>();
然后在方法中调用set方法添加数据(request)
threadLocal.set(request);
在拦截器中调用get方法就可以拿到HttpServletRequest对象,然后给requestTemplate 添加上对应的请求头中的数据:aa,bbb
HttpServletRequest httpServletRequest = CartController.threadLocal.get();
requestTemplate.header("userId",httpServletRequest.getHeader("aa"));
requestTemplate.header("userTempId",httpServletRequest.getHeader("bbb"));
思路3实现:使用spring提供的对象RequestContextHolder直接在拦截器类获取request对象
//RequestContextHolder对象中获取一个线程内所共享的HttpServletRequest对象
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
//获取到了request对象剩下的同上
思路3的在Controller使用模块中通过RequestContextHolder隐式获取请求头数据
直接在使用模块获取ServletRequestAttributes对象,不需要在参数添加@RequestHeader(value = "xxx")
因为刚才已经用到了request对象
// 获取ServletRequestAttributes对象
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest requestAttributesRequest = requestAttributes.getRequest();
String userId = requestAttributesRequest.getHeader("userId");
String userTempId = requestAttributesRequest.getHeader("userTempId") ;
代码优化:
1.将从RequestContextHolder对象中读取xx和xx数据的代码封装到一个工具类(utilxx),并使用一个实体类返回相关数据:
public class Utils {
public static UserInfoVo userInfo() {
// 获取请求对象
ServletRequestAttributes requestAttributes
= (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
// 判断requestAttributes是否为空
if(requestAttributes != null) {
// 获取request对象
HttpServletRequest request = requestAttributes.getRequest();
// 从请求对象中获取xx和xx数据
String userid = request.getHeader("id");
String username = request.getHeader("name");
// 封装数据到xxx对象中
User user = new user() ;
if(!StringUtils.isEmpty(userId)) {
userAuthInfoVo.setUserId(Long.parseLong(userId));
}
userAuthInfoVo.setUserTempId(username);
// 返回
return user ;
}
return null ;
}
}
2.FeignClientInterceptor抽取成公共组件
为了实现FeignClientInterceptor的复用,那么此时可以将FeignClientInterceptor抽取成一个公共的组件
@Slf4j
@Component
public class FeignClientInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
log.info("FeignClientInterceptor拦截器执行了....");
User user = User.getUser();
if(user != null) {
Long userId = user.getUserId();
if(userId != null) {
template.header("userId" , String.valueOf(userId)) ;
}
template.header("username" , userAuthInfo.getUsername()) ;
}
}
}
自定义注解:
因为抽取公共类后包路径不一样会导致扫描不到所以,可以使用自定义注解或@Import解决,这里使用自定义注解文章来源:https://www.toymoban.com/news/detail-455228.html
@Target(value = ElementType.TYPE)
@Retention(value = RetentionPolicy.RUNTIME)
@Import(value = EnableFeignClientInterceptor.class)
public @interface EnableFeignClientInterceptor {
}
启动类加上@EnableFeignClientIntercepto即可使用文章来源地址https://www.toymoban.com/news/detail-455228.html
到了这里,关于feign微服务之间传递请求头数据的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!