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



    前言

    在使用feign 接口进行远程或者内部服务间的方法调用时,有时会遇到明明声明了GET请求,但是feign 依然使用POST进行发送,或者使用GET 请求是明明已经放入了参数,依然提示参数值是空的,使用了异步线程调用feign 接口总是失效。


    提示:以下是本篇文章正文内容,下面案例可供参考

    一、声明GET请求实际用POST ?

    1.1 例子:

    @GetMapping("/api/test1")
    R<Boolean> test1(@RequestHeader("token') String token,
                                           String endTime);
    
    • 1
    • 2
    • 3

    报错:
    在这里插入图片描述

    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);
    
    • 1
    • 2
    • 3

    报错:
    在这里插入图片描述

    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
    
    • 1

    你的controller方法可以是这样的:

    @GetMapping("/some-endpoint")
    public String sayHello(String name) {
      return "Hello, " + name;
    }
    
    • 1
    • 2
    • 3
    • 4

    你可以看到,并没有显式使用@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();
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    如果在本地进行单元测试,因为主线程走完后,容器就被关闭了,所以需要阻塞主线程,这样子线程才能正常执行;

    四、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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    当调用该方法时,传入不同的值给这两个参数:

    exampleClient.getData("value1", "value2");
    
    • 1

    Feign发送请求时,将会合并这两个参数的值,并作为单个请求头发送给服务端:

    header: value1, value2
    注意,这里的参数名是与请求头的键名相同的,导致它们会被自动合并为单个请求头的值。如果不希望请求参数被合并,需要设置参数的名称应该设置不相同。
    在项目开发中通常会对feign 接口 实现 RequestInterceptor 对其传递 的参数统一作处理,此时就需要注意我们在调用feign 接口方法是否对请求头设置了相同的参数名,导致接口调用异常。

    总结

    本文对feign 接口在使用过程中遇到的常见问题,进行分析以及给出解决办法。

  • 相关阅读:
    freeipa server副本同步中断,两主节点数据不一致
    ChatGPT⼊门到精通(6):ChatGPT 提问设计
    第一百五十五回 如何获取位置信息
    selenium源码通读·9 |DesiredCapabilities类分析
    计算机简史:所有这些时刻,终将流失在时光中,一如眼泪消失在雨里
    Pytorch从零开始实战08
    LeetCode 面试题 08.05. 递归乘法
    微信小程序开发之图片压缩方案
    python从入门到实践:数据类型、文件处理
    linux服务器配置深度学习环境一些命令
  • 原文地址:https://blog.csdn.net/l123lgx/article/details/132980991