解决Spring Cloud feign GET请求无法用实体传参的问题
作者:程序员DMZ 发布时间:2023-11-17 14:14:05
Spring Cloud feign GET请求无法用实体传参
代码如下:
@FeignClient(name = "eureka-client", fallbackFactory = FallBack.class, decode404 = true, path = "/client")
public interface FeignApi {
// @PostMapping("/hello/{who}")
// String hello(@PathVariable(value = "who") String who) throws Exception;
@GetMapping("/hello")
String hello(Params params) throws Exception;
}
调用报错:
feign.FeignException: status 405 reading FeignApi#hello(Params)
解决办法
改用post请求,添加@RequestBodey注解
新增@SpringQueryMaq注解,如下:
@GetMapping("/hello")
String hello(@SpringQueryMap Params params) throws Exception;
Spring Cloud Feign异步调用传参问题
各个子系统之间通过feign调用,每个服务提供方需要验证每个请求header里的token。
public void invokeFeign() throws Exception {
feignService1.method();
feignService2.method();
feignService3.method();
....
}
定义拦截每次发送feign调用 * RequestInterceptor的子类,每次发送feign请求前将token带入请求头
@Configuration
public class FeignTokenInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
public void apply(RequestTemplate template) {
//上下文环境保持器,拿到刚进来这个请求包含的数据,而不会因为远程数据请求头被清除
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();//老的请求
if (request != null) {
//同步老的请求头中的数据,这里是获取cookie
String cookie = request.getHeader("token");
template.header("token", cookie);
}
}
.....
}
这样便能实现系统间通过同步方式feign调用的认证问题。但是如果需要在invokeFeign方法中feignService3的方法调用比较耗时,并且invokeFeign业务并不关心feignService3.method()方法的执行结果,此时该怎么办。
方案1
修改feignService3.method()方法,将其内部实现修改为异步,这种方案依赖服务的提供方,如果feignService3服务是其他业务部门维护,并且无法修改实现为异步,此时只能采取方案2.
方案2
通过线程池调用feignServie3.method()
public void invokeFeign() throws Exception {
feignService1.method();
feignService2.method();
executor.submit(()->{
feignService3.method();
});
....
}
怀着期待的心情开启了尝试,你会发现调用feignService3方法并没有成功,查看日志你将会发现是由于feign发送request请求的header中未携带token导致。于是百度了下feign异步调用传参,网上大部分的解决方案,如下
public void invokeFeign() throws Exception {
feignService1.method();
feignService2.method();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
executor.submit(()->{
RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);
feignService3.method();
});
}
}
添加了上面的代码后,实测无效,此时确实有些束手无策。但是真的没无效吗?我仔细比对通过上述手段解决问题的博客,他们的业务代码和我的代码不同之处。确实有不同,比如这篇。其代码如下
@Override
public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
OrderConfirmVo confirmVo = new OrderConfirmVo();
MemberResVo memberResVo = LoginUserInterceptor.loginUser.get();
//从主线程中获得所有request数据
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
//1、远程查询所有地址列表
RequestContextHolder.setRequestAttributes(requestAttributes);
List<MemberAddressVo> address = memberFeignService.getAddress(memberResVo.getId());
confirmVo.setAddress(address);
}, executor);
//2、远程查询购物车所选的购物项,获得所有购物项数据
CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
//放入子线程中request数据
RequestContextHolder.setRequestAttributes(requestAttributes);
List<OrderItemVo> items = cartFeginService.getCurrentUserCartItems();
confirmVo.setItem(items);
}, executor).thenRunAsync(()->{
RequestContextHolder.setRequestAttributes(requestAttributes);
List<OrderItemVo> items = confirmVo.getItem();
List<Long> collect = items.stream().map(item -> item.getSkuId()).collect(Collectors.toList());
//远程调用查询是否有库存
R hasStock = wmsFeignService.getSkusHasStock(collect);
//形成一个List集合,获取所有物品是否有货的情况
List<SkuStockVo> data = hasStock.getData(new TypeReference<List<SkuStockVo>>() {
});
if (data!=null){
//收集起来,Map<Long,Boolean> stocks;
Map<Long, Boolean> map = data.stream().collect(Collectors.toMap(SkuStockVo::getSkuId, SkuStockVo::getHasStock));
confirmVo.setStocks(map);
}
},executor);
//feign远程调用在调用之前会调用很多 * ,因此远程调用会丢失很多请求头
//3、查询用户积分
Integer integration = memberResVo.getIntegration();
confirmVo.setIntegration(integration);
//其他数据自动计算
CompletableFuture.allOf(getAddressFuture,cartFuture).get();
return confirmVo;
}
我们看的出来,他的业务代码即使是开启多线程,也是等最后线程里的任务都执行完成后,业务方法才结束返回,而我的业务方法并不会等feignService3调用完成结束,抱着尝试的心态,我调整了下代码添加了CountDownLatch,让业务方法等待feign调用结束后在返回。
public void invokeFeign() throws Exception {
feignService1.method();
feignService2.method();
CountDownLatch latch = new CountDownLatch(1);
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
executor.submit(()->{
RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);
feignService3.method();
latch.countDown();
});
latch.await();
}
}
不如所料,调用成功了。到这里看似是解决了问题,但是与我想象的异步差别太大了,最终业务线程还是需要等待feignService3.method()调用业务方法才能返回,而且异步场景如发送短信、消息推送,记录日志可能调用耗时,业务方法可不想等待他们执行结束,此时该怎么解决?
只能翻源码 ServletRequestAttributes.java
首先看到了注释,这给了我灵感
Servlet-based implementation of the {@link RequestAttributes} interface. <p>Accesses objects from servlet request and HTTP session scope,
with no distinction between "session" and "global session".
从servlet请求和HTTP会话范围访问对象,"session"和"global session"作用域没有区别。对呀会不会是因为header中的参数是request作用域的原因呢,因为请求结束,所以即使在子线程设置请求头,也取不到原因。回到请求 * RequestInterceptor查看获取token地方
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//老的请求
HttpServletRequest request = attributes.getRequest();
if (request != null) {
//同步老的请求头中的数据,这里是获取cookie
String cookie = request.getHeader("token");
template.header("token", cookie);
}
果然如此,从attributes中获取request,然后从request中获取token。但是没有考虑到request请求结束,request作用域的问题,此时肯定取不到header里的token了。
那么该怎么解决呢?思路不能变,肯定还是围绕着ServletRequestAttributes展开,发现他有两个方法getAttributes和setAttribute,而且这俩方法都支持两个作用域request、session。
@Override
public Object getAttribute(String name, int scope) {
if (scope == SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException(
"Cannot ask for request attribute - request is not active anymore!");
}
return this.request.getAttribute(name);
}
else {
HttpSession session = getSession(false);
if (session != null) {
try {
Object value = session.getAttribute(name);
if (value != null) {
this.sessionAttributesToUpdate.put(name, value);
}
return value;
}
catch (IllegalStateException ex) {
// Session invalidated - shouldn't usually happen.
}
}
return null;
}
}
@Override
public void setAttribute(String name, Object value, int scope) {
if (scope == SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException(
"Cannot set request attribute - request is not active anymore!");
}
this.request.setAttribute(name, value);
}
else {
HttpSession session = obtainSession();
this.sessionAttributesToUpdate.remove(name);
session.setAttribute(name, value);
}
}
既然我们的业务方法调用(HttpServletRequest)不会等待feignService3.method,我们可以通过
ServletRequestAttributes.setAttributes指定作用域为session呀。
此时invokeFeign代码如下
public void invokeFeign() throws Exception {
feignService1.method();
feignService2.method();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
//在ServeletRequestAttributes中设置token,作用域为session
attributes.setAttribute("token",attributes.getRequest().getHeader("token"),1);
executor.submit(()->{
RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);
feignService3.method();
});
}
}
然后RequestInterceptor.apply方法也做响应调整,如下
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//老的请求
HttpServletRequest request = attributes.getRequest();
String token = (String) attributes.getAttribute("token",1);
template.header("token",token);
if (request != null) {
//同步老的请求头中的数据,这里是获取cookie
String cookie = request.getHeader("token");
template.header("token", cookie);
}
问题得以圆满解决。
来源:https://daimingzhi.blog.csdn.net/article/details/90760993


猜你喜欢
- 1. kotlin 数值型fun main() { // 整数型 val a: Byte
- 本文实例为大家分享了java实现登录验证码功能的具体代码,供大家参考,具体内容如下登录验证码登录验证是大多数登录系统都会用到的一个功能,它的
- 自动生成的代码报错解决办法:把自动xml文件中自动生成的二级缓存注释掉来源:https://blog.csdn.net/weixin_447
- 摘要在J2SE 1.5的java.util.concurrent包(下称j.u.c包)中,大部分的同步器(例如锁,屏障等等)都是基于Abst
- 前言最近开发中用到许多对话框,之前都是在外面的代码中创建AlertDialog并设置自定义布局实现常见的对话框,诸如更新提示等含有取消和删除
- 开发环境win10Android Studio效果用于多级菜单展示,或选择。如 每个省,市,县;如 树木的病虫害;关键代码 @overrid
- 数据库事务是被当作单个工作单元的操作序列。这些操作要么全部完成或全部不成功。事务管理是面向企业应用程序,以确保数据的完整性和一致性RDBMS
- 做Android开发两年的时间,技术稍稍有一些提升,刚好把自己实现的功能写出来,记录一下,如果能帮助到同行的其他人,我也算是做了件好事,哈哈
- 异步、多线程、任务、并行编程之一:选择合适的多线程模型本篇概述:@FCL4.0中已经存在的线程模型,以及它们之间异同点;@多线程编程模型的选
- 目录1.项目gitthub地址链接: https://github.com/baisul/generateCode.git切换到master
- iOS中有封装好的选择图片后长按出现动画删除效果,效果如下 而Android找了很久都没有找到有这样效果的第三方组件,
- 最近因为赶项目进度,因此将本来要用原生控件实现的界面,自己做了H5并嵌入webview中。发现点击H5中 input type="
- 前言其实小编之前一直都是用的Java来开发Android,但是工作需求,开始了Kotlin的编程,接触到了JetPack,发现其中的Navi
- 这篇文章首先介绍了在SpringBoot中如何获得项目的编译时间和版本号,并向外提供接口,然后介绍了介绍了新版maven获得时间戳时区错误的
- 通过反射根据提供的表名、POJO类型、数据对象自动生成sql语句。如名为 User 的JavaBean与名为 user 的数据库表对应,可以
- public static class EncryptAndDecrypt { &
- 最近接触了Android自定义控件,涉及到自定义xml中得属性(attribute),其实也很简单,但是写着写着,发现代码不完美了,就是在a
- java实现字符串匹配暴力匹配/** * 暴力匹配 * * @param str1 需要找的总字符串 * @param str2 需要找到的
- 本文实例讲述了Android编程之绘制文本(FontMetrics)实现方法。分享给大家供大家参考,具体如下:Canvas 作为绘制文本时,
- 手机权限检查和申请简介使用flutter进行app开发,一定会用到手机的部分权限,包括通知推送、定位、相册、存储、相机、麦克风等。而权限的检