文章目录
- 前言
- 一、声明GET请求实际用POST ?
- 1.1 例子:
- 1.2 原因:
- 二、GET请求放入了参数值却找不到?
- 2.1 例子:
- 2.2 原因:
- 2.3 spring-mvc http 请求中为什么可以:
- 三、异步线程无法调用feign 接口 ?
- 3.1 例子:
- 3.2 原因:
- 四、feign header 相同名字请求头合并 ?
- 总结
前言
在使用feign 接口进行远程或者内部服务间的方法调用时,有时会遇到明明声明了GET请求,但是feign 依然使用POST进行发送,或者使用GET 请求是明明已经放入了参数,依然提示参数值是空的,使用了异步线程调用feign 接口总是失效。
提示:以下是本篇文章正文内容,下面案例可供参考
一、声明GET请求实际用POST ?
1.1 例子:
@GetMapping("/api/test1")
R<Boolean> test1(@RequestHeader("token') String token,String endTime);
报错:
1.2 原因:
-
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 的方式。
二、GET请求放入了参数值却找不到?
2.1 例子:
@GetMapping("/api/test2")
R<Boolean> test2(@RequestHeader("token') String token,@RequestParam String endTime);
报错:
2.2 原因:
-
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"的参数内。
2.3 spring-mvc http 请求中为什么可以:
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。
三、异步线程无法调用feign 接口 ?
3.1 例子:
private static ExecutorService executor = Executors.newFixedThreadPool(2);CompletableFuture<Void> bFuture = CompletableFuture.runAsync(() -> {/远程调用B服务,查某数据。}, executor);bFuture.get();
feing 接口没有调通,而且没有日志输出:
3.2 原因:
在使用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 header 相同名字请求头合并 ?
Feign在处理请求头时,默认情况会合并具有相同名字的参数。合并后的值将作为单个请求头的值发送。例如,在Feign接口定义中有两个相同名字的请求头参数:
@FeignClient(name = "example", url = "http://example.com")
public interface ExampleClient {@RequestMapping(method = RequestMethod.GET, value = "/api/example")ResponseEntity<String> getData(@RequestHeader("header") String header1, @RequestHeader("header") String header2);
}
当调用该方法时,传入不同的值给这两个参数:
exampleClient.getData("value1", "value2");
Feign发送请求时,将会合并这两个参数的值,并作为单个请求头发送给服务端:
header: value1, value2
注意,这里的参数名是与请求头的键名相同的,导致它们会被自动合并为单个请求头的值。如果不希望请求参数被合并,需要设置参数的名称应该设置不相同。
在项目开发中通常会对feign 接口 实现 RequestInterceptor 对其传递 的参数统一作处理,此时就需要注意我们在调用feign 接口方法是否对请求头设置了相同的参数名,导致接口调用异常。
总结
本文对feign 接口在使用过程中遇到的常见问题,进行分析以及给出解决办法。