在使用feign 接口进行远程或者内部服务间的方法调用时,有时会遇到明明声明了GET请求,但是feign 依然使用POST进行发送,或者使用GET 请求是明明已经放入了参数,依然提示参数值是空的,使用了异步线程调用feign 接口总是失效。
提示:以下是本篇文章正文内容,下面案例可供参考
@GetMapping("/api/test1")
R<Boolean> test1(@RequestHeader("token') String token,
String endTime);
报错:
Feign 在默认情况下会将所有的请求方法都认为是 GET 请求
。但是,如果请求的方法参数没有被注解标注,或者只使用了 @RequestBody 注解,那么 Feign 会将请求方法自动处理为 POST 请求
(因为它认为可能有参数需要在请求体中发送)。
如果 Feign 的接口方法参数没有被 @RequestParam 或 @PathVariable 等注解标注,则 Feign 会将该请求当作是 POST 请求,并且尝试将参数放到 POST 请求的请求体中去,即使你的方法在定义时倾向于 GET 请求
。
此规则源自于 Feign 的设计原则,它认为如果方法参数没有任何注解(或者只有 @RequestBody 注解),那么这些参数应该处于请求体中,而请求体一般的使用场景是在 POST 或者 PUT 请求中
,所以 Feign 采用了 POST 请求作为默认值。
如果你希望使用 GET 请求并且参数需要在 URL 中传递,那么你需要为方法参数添加 @RequestParam 或 @PathVariable
等注解,这样 Feign 就会将这些参数添加到 GET 请求的 URL 中,而不是采用 POST 的方式。
@GetMapping("/api/test2")
R<Boolean> test2(@RequestHeader("token') String token,
@RequestParam String endTime);
Feign无法获取未标注@RequestParam的参数名称,这是因为Java编译器默认是不会把方法的参数名称编译进入class文件中的
。
Java编译器在编译程序源码的时候,只会保留方法的参数类型和参数的顺序,但不会保留参数名称。也就是说,在.class文件中,方法的参数名称被替换为了通用的名称,比如arg0, arg1, arg2等。原本的参数名称信息在编译后就丢失了。而因为 Feign 是在运行时通过反射解析方法签名来构造请求,所以它无法获取未标注@RequestParam的参数名称。
-在Spring Cloud中使用Feign时,@RequestParam注解用于将方法参数绑定到请求的query参数
。在Feign中,如果你将一个方法参数标注为@RequestParam却没有为它指定具体的参数名,那么Feign在构建请求时就无法知道这个参数应该绑定到query参数的哪一个名称上。
这种情况在所有使用了反射机制的 Java 程序中都会遇到,不仅仅是 Feign。为了解决这个问题,Java 提供了一种方式将参数名称保留在 .class 文件中,需要在编译时使用 -parameters 选项,或者在源码中用 @Param 这样的注解显式提供参数名。
所以,在使用 Feign 的时候你必须为@RequestParam注解指定参数名称,这样Feign在运行时就能通过这个名称知道应该如何绑定参数到请求
。例如:@RequestParam(“name”) String name,这样 Feign 就知道应该将这个参数值放到请求中名为"name"的参数内。
Spring MVC 可以通过其他方式获取到方法参数的名称。Spring 内部使用了一种叫做 LocalVariableTableParameterNameDiscoverer
的技术,它使用了 Java 字节码操作库 ASM 来分析 .class 文件的局部变量表(Local Variable Table),以此来获取方法参数的名称。对于 Feign 而言,它并没有内置这样的机制来获取方法参数的名称
,所以就需要显式指定 @RequestParam 的 name。
当你在Spring MVC中创建一个Controller方法来处理HTTP请求时,方法参数和请求参数之间的绑定可以通过多种方式实现。
比如,你可以在方法中直接使用与请求参数相同名字的参数,Spring MVC会自动匹配。这是因为Spring MVC在处理请求时,会利用Java反射机制获取方法参数的名称,然后根据名称去匹配请求中的参数。这得益于Spring框架的一部分叫做DataBinder,它负责将请求参数映射到控制器方法的参数。
假设你的HTTP请求是这样的:
GET /some-endpoint?name=John
你的controller方法可以是这样的:
@GetMapping("/some-endpoint")
public String sayHello(String name) {
return "Hello, " + name;
}
你可以看到,并没有显式使用@RequestParam注解,Spring MVC在处理请求时已经自动将请求参数"name"绑定到了方法参数name上。
然而,这只在Spring MVC中有效。对于Feign来说,这种机制就不适用了,因此须显式指定@RequestParam的name。
private static ExecutorService executor = Executors.newFixedThreadPool(2);
CompletableFuture<Void> bFuture = CompletableFuture.runAsync(() -> {
/远程调用B服务,查某数据。
}, executor);
bFuture.get();
feing 接口没有调通,而且没有日志输出:
在使用Feign进行服务调用时,如果在新的线程内部进行Feign接口调用无效或失败,这个问题通常与Spring Cloud的上下文传递有关
;Spring Cloud中的许多特性(例如Hystrix,Ribbon, Feign等)都依赖于ThreadLocal存储上下文或其它信息。如果在新的线程中启动了一个Feign调用,新线程通常不会继承主线程的ThreadLocal,所以这可能导致调用失败
。
所以这里需要线程主线程拿到上下文然后进行调用:
private static ExecutorService executor = Executors.newFixedThreadPool(2);
//获取"主线程"的请求上下文
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
CompletableFuture<Void> bFuture = CompletableFuture.runAsync(() -> {
//将"主线程"的请求上下文,设置在当前“异步线程上下文”中。
RequestContextHolder.setRequestAttributes(requestAttributes);
//2、远程调用B服务,查某数据。
}, executor);
// 本地单元测试打开下面一行,用于阻塞主线程
// CompletableFuture.allOf(bFuture).get();
如果在本地进行单元测试,因为主线程走完后,容器就被关闭了,所以需要阻塞主线程,这样子线程才能正常执行;
本文对feign 接口在使用过程中遇到的常见问题,进行分析以及给出解决办法。