关于OpenFeign 接口参数定义的问题

文章探讨了Feign接口中GET请求被误识别为POST、GET请求参数丢失、异步线程调用失败及header合并的问题,并提供了相应的解决策略和代码示例。
摘要由CSDN通过智能技术生成


前言

在使用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 接口在使用过程中遇到的常见问题,进行分析以及给出解决办法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值