记一次 Feign 的坑

6,037 阅读4分钟

事件回顾

起因

事情是这样的,最近在代码中需要使用 Feign 调用第三方服务。所以就是标准的一套操作:

    1. 引入第三方服务 Jar 包。
    1. @EnableFeignClients 注解注入 Bean。
    1. @Autowired 引入 Bean。

然后也没有在意到 Idea 已经有警告了,直接启动服务了。然后就出现如下错误:

Description:

Field api in com.xxx.service.impl.ServiceImpl required a bean of type 'com.xxx.api.Api' that could not be found.

The injection point has the following annotations:
	- @org.springframework.beans.factory.annotation.Autowired(required=true)

Action:

Consider defining a bean of type 'com.xxx.api.Api' in your configuration.

PS: Idea 的告警为 Could not autowire. No beans of 'Api' type found.

经过

然后结合 Idea 的警告与日志直接就配置 Autowired 的参数 @Autowired(required = false)算是暂时解决了项目能启动以及 Idea 的警告。

但是。。。 但是。。。 但是。。。

image.png

在调试代码的时候出现一个奇怪的错误,引入的 Bean 居然为空,这让我感到异常困惑,加上对 Spring 不是很熟悉,让我顿感不安。

一顿百度、谷歌打断点调试,其中有个说的有道理

  • EnableFeignClients 扫描路径与 ComponentScan 扫描冲突 检查后配置后发现没有问题。

直到查看到代码中关于两个 Feign 的导入才发现到了端倪。

  • import org.springframework.cloud.openfeign.EnableFeignClients;
  • import org.springframework.cloud.netflix.feign.FeignClient;

终于问题的原因找到了项目中 FeignClientEnableFeignClients 的版本不一致导致 Bean 注入失败。 @Autowired(required = false) 忽略了 Bean 的注入。最终导致NullPointerException

结果

本以为找到问题就可以解决了,但是面对第三方的 Jar 包,显然直接修改代码是不可能的。

那替换自己的呢?

那也是不行的,因为第四方服务、第五方服务都是 openfeign 的注解。

思来想去最后只能在自己的项目中基于 openfeign 的实现与原业务相同的接口。其实就是复制代码更改注解,其他的啥也不是。代码大致如下:

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@FeignClient( name = "service",
        path = "/api/v1")
public interface ApiFeignClient {
    @RequestMapping(
            method = {RequestMethod.POST},
            value = {"/query"},
            consumes = {"application/json"}
    )
    ResponseBody query(@RequestBody RequestDto reqDto);

    @RequestMapping(
            method = {RequestMethod.GET},
            value = {"/list"},
            consumes = {"application/json"}
    )
    ResponseBody list(@RequestParam("name") String name);
}

然后代码中使用我们自己的 Bean,类似下面:

@Autowired
private ApiFeignClient api;

这样有缺点就是第三方服务的逻辑更改并不能同步到你的服务。所以这是一种后知后觉的方法去解决问题。本质上还是需要第三方服务实现基于 openfeign 的 Jar 包。

Feign 总结

经过这事之后,也查了很多资料,看了很多博客。这里总结一下关于 Feign 的知识点。

Feign 是什么?

Feign 是 Netflix 开发的声明式、模板化的HTTP客户端, Feign 可以帮助我们更快捷、优雅地调用 HTTP。

简单的说 Feign 通过将注解处理为模板化请求来工作。参数在输出之前直接应用于这些模板。

Netflixfeign 与 Openfeign 区别

Spring Cloud Feign是基于Netflix feign实现,整合了Spring Cloud Ribbon和Spring Cloud Hystrix,除了提供这两者的强大功能外,还提供了一种声明式的Web服务客户端定义的方式。

具体的可以参考 stackoverflow 的讨论。

注解的参数

主要说明 EnableFeignClientsFeignClient

  • EnableFeignClients 注解参数
名称解释
value为 basePackages 属性的别名,允许使用更简洁的书写方式。
basePackages设置自动扫描带有 @FeignClient 注解的基础包路径。
basePackageClassesbasePackages 属性的安全替代属性。扫描 @FeignClient修饰的类。
defaultConfiguration该属性用来自定义所有Feign客户端的配置,使用 @Configuration进行配置。
clients设置由 @FeignClient 注解修饰的类列表。注意 clients 不是空数组,则不通过类路径自动扫描功能来加载 FeignClient。
  • FeignClient 注解参数 | 名称 | 解释| | --- | --- | | name、value、serviceId | 三者作用基本一致。name指定 FeignClient 的名称,如果项目使用了 Ribbon,name属性会作为微服务的名称,用于服务发现。serviceId 已经废弃. | qualifier | 指定别名接本没用过。对应的是 @Qualifier注解 | | url | 指定 FeignClient 调用的地址,一般用于调试程序。 | | decode404 | 调用出现 http404 错误且该字段位为true,会调用decoder进行解码,否则抛出异常 | | configuration | Feign配置类,可以自定义Feign的 Encoder、Decoder、LogLevel、Contract。| | fallback| 定义容错的处理类,当调用远程接口异常时,会调用对应接口的异常逻辑,注意 fallback 指定的类必须实现 @FeignClient 标记的接口 | fallbackFactory| 用于生成fallback类示例,实现每个接口通用的容错逻辑,减少重复的代码。| | path | 定义当前 FeignClient 的统一前缀。| | primary| 是否将伪代理标记为主 Bean,默认为true。|

参考资料