openFeign各个参数详解

openFeign各个参数详解,第1张

value、name

value和name的作用一样,如果没有配置url那么配置的值将作为服务名称,用于服务发现。反之只是一个名称。

contextId

我们不想将所有的调用接口都定义在一个类中,有一种解决方案就是为每个Client手动指定不同的contextId,这样就不会冲突了。

url

url用于配置指定服务的地址,相当于直接请求这个服务,不经过Ribbon的服务选择。像调试等场景可以使用。

decode404

当调用请求发生404错误时,decode404的值为true,那么会执行decoder解码,否则抛出异常。

configuration

configuration是配置Feign配置类,在配置类中可以自定义Feign的Encoder、Decoder、LogLevel、Contract等。

fallback

定义容错的处理类,也就是回退逻辑,fallback的类必须实现Feign Client的接口,无法知道熔断的异常信息。

fallbackFactory

也是容错的处理,可以知道熔断的异常信息。

path

path定义当前FeignClient访问接口时的统一前缀,比如接口地址是/user/get, 如果你定义了前缀是user, 那么具体方法上的路径就只需要写/get 即可。

primary

primary对应的是@Primary注解,默认为true,官方这样设置也是有原因的。当我们的Feign实现了fallback后,也就意味着Feign Client有多个相同的Bean在Spring容器中,当我们在使用@Autowired进行注入的时候,不知道注入哪个,所以我们需要设置一个优先级高的,@Primary注解就是干这件事情的。

qualifier

qualifier对应的是@Qualifier注解,使用场景跟上面的primary关系很淡,一般场景直接@Autowired直接注入就可以了。

1跟踪抛出异常的堆栈,发现在对返回结果的json解析中抛出异常

2为什么会解析json失败呢,我们单独调用feign对应的接口是正常的,json也是正常可以解析的

3难道feign的处理过返回的内容,又去跟了下fegin处理过程发现从response获取到流并没有任何异常,难道是出在了源头?但是源头又没有任何异常,此时思绪已经混乱,试着在google上查找有没有相关的问题,没想到在feign的github上找到类似问题 >

本文会基于Feign源码,看看Feign到底是怎么实现远程调用

上文中,我们的 user-service 服务需要调用远程的 order-service 服务完成一定的业务逻辑,而基本实现是order-service提供一个spi的jar包给user-service依赖,并且在user-service的启动类上添加了一个注解

这个注解就是@EnableFeignClients,接下来我们就从这个注解入手,一步一步解开Feign的神秘面纱

该注解类上的注释大概的意思就是:

扫描那些被声明为 Feign Clients (只要有 orgspringframeworkcloudopenfeignFeignClient 注解修饰的接口都是Feign Clients接口)的接口

下面我们继续追踪源码,看看到底什么地方用到了这个注解

利用IDEA的查找调用链快捷键,可以发现在class类型的文件中只有一个文件用到了这个注解

OK,下面主要就是看这个类做了什么

通过UML图我们发现该类分别实现了 ImportBeanDefinitionRegistrar , ResourceLoaderAware 以及 EnvironmentAware 接口

这三个接口均是spring-framework框架的spring-context模块下的接口,都是和spring上下文相关,具体作用下文会分析

总结下来就是利用这两个重要属性,一个获取应用配置属性,一个可以加载classpath下的文件,那么FeignClientsRegistrar持有这两个东西之后要做什么呢?

上面将bean配置类包装成 FeignClientSpecification ,注入到容器。该对象非常重要,包含FeignClient需要的 重试策略 , 超时策略 , 日志 等配置,如果某个FeignClient服务没有设置独立的配置类,则读取默认的配置,可以将这里注册的bean理解为整个应用中所有feign的默认配置

由于 FeignClientsRegistrar 实现了 ImportBeanDefinitionRegistrar 接口,这里简单提下这个接口的作用

我们知道在spring框架中,我们如果想注册一个bean的话主要由两种方式:自动注册/手动注册

知道了 ImportBeanDefinitionRegistrar 接口的作用,下面就来看下 FeignClientsRegistrar 类是何时被加载实例化的

通过IDEA工具搜索引用链,发现该类是在注解@EnableFeignClients上被import进来的,文章开始的中有

这里提下@Import注解的作用

该注解仅有一个属性value,使用该注解表明导入一个或者多个@Configuration类,其作用和xml文件中的<import>等效,其允许导入@Configuration类,ImportSelector接口/ImportBeanDefinitionRegistrar接口的实现,也同样可以导入一个普通的组件类

注意,如果是XML或非@Configuration的bean定义资源需要被导入的话,需要使用@ImportResource注解代替

这里我们导入的FeignClientsRegistrar类正是一个ImportBeanDefinitionRegistrar接口的实现

FeignClientsRegistrar重写了该接口的 registerBeanDefinitions 方法,该方法有两个参数注解元数据 metadata 和bean定义注册表 registry

该方法会由spring负责调用,继而注册所有标注为@FeignClient注解的bean定义

下面看registerBeanDefinitions方法中的第二个方法,在该方法中完成了所有@FeignClient注解接口的扫描工作,以及注册到spring中,注意这里注册bean的类型为 FeignClientFactoryBean ,下面细说

总结一下该方法,就是扫描@EnableFeignClients注解上指定的basePackage或clients值,获取所有@FeignClient注解标识的接口,然后将这些接口一一调用以下 两个重要方法 完成 注册configuration配置bean 和注册 FeignClient bean

断点位置相当重要

BeanDefinitionBuilder definition = BeanDefinitionBuildergenericBeanDefinition(FeignClientFactoryBeanclass);

这里是利用了spring的代理工厂来生成代理类,即这里将所有的 feignClient的描述信息 BeanDefinition 设定为 FeignClientFactoryBean 类型,该类继承自FactoryBean,因此这是一个代理类,FactoryBean是一个工厂bean,用作创建代理bean,所以得出结论,feign将所有的 feignClient bean定义的类型包装成 FeignClientFactoryBean

最终其实就是存入了BeanFactory的beanDefinitionMap中

那么代理类什么时候会触发生成呢? 在spring 刷新容器时 ,会根据beanDefinition去实例化bean,如果beanDefinition的beanClass类型为代理bean,则会调用其 T getObject() throws Exception; 方法生成代理bean,而我们实际利用注入进来的FeignClient接口就是这些一个个代理类

这里有一个需要注意的点,也是开发中会遇到的一个 启动报错点

如果我们同时定义了两个不同名称的接口 (同一个包下/或依赖方指定全部扫描我们提供的 @FeignClient ),且这两个 @FeignClient 接口注解的 value/name/serviceId 值一样的话,依赖方拿到我们的提供的spi依赖,启动类上 @EnableFeignClients 注解扫描能同时扫描到这两个接口,就会 启动报错

原因就是Feign会为每个@FeignClient注解标识的接口都注册一个以serviceId/name/value为key,FeignClientSpecification类型的bean定义为value去spring注册bean定义,又默认不允许覆盖bean定义,所以报错

官方提示给出的解决方法要么改个@FeignClient注解的serviceId,name,value属性值,要么就开启spring允许bean定义覆写

至此我们知道利用在springboot的启动类上添加的@EnableFeignClients注解,该注解中import进来了一个手动注册bean的 FeignClientsRegistrar注册器 ,该注册器会由spring加载其 registerBeanDefinitions方法 ,由此来扫描所有@EnableFeignClients注解定义的basePackages包路径下的所有标注为@FeignClient注解的接口,并将其注册到spring的bean定义Map中,并实例化bean

下一篇博文中,我会分析为什么我们在调用(@Resource)这些由@FeignClient注解的bean的方法时会发起 远程调用

Spring Cloud Feign 用于微服务的封装,通过接口代理的实现方式让微服务调用变得简单,让微服务的使用上如同本地服务。但是它在传参方面不是很完美。在使用 Feign 代理 GET 请求时,对于简单参数(基本类型、包装器、字符串)的使用上没有困难,但是在使用对象传参时却无法自动的将对象包含的字段解析出来。

对象传参是很常见的 *** 作,虽然可以通过一个个参数传递来替代,但是那样就太麻烦了,所以必须解决这个问题。

我在网上看到有人用 @RequestBody 来注解对象参数,我在尝试后发现确实可用。这个方案实际使用 body 体装了参数(使用的是 GET 请求),但是这个方案有些问题:

于是我继续寻找答案,发现可以使用 @SpringQueryMap 仅添加在 consumer 的参数上就能自动对 Map 类型参数编码再拼接到 URL 上。而我用的高版本的 Feign,可以直接把对象编码。

可是正当我以为得到正解时,却发现还是有问题:

我明明在 Date 类型的字段上加上了 @DateTimeFormat(pattern = "yyyy-MM-dd") ,却没有生效,他用自己的方式进行了编码(或者说序列化),而且官方确实没有提供这种格式化方式。

又一番找寻后发现了一位大佬自己实现了一个注解转换替代 @SpringQueryMap,并实现了丰富的格式化功能 ORZ(原文链接: Spring Cloud Feign实现自定义复杂对象传参 ),只能说佩服佩服。但是我没有那样的技术,又不太想复制粘贴他那一大堆的代码,因为出了问题也不好改,所以我还是想坚持最大限度地使用框架,最小限度的给框架填坑。

终于功夫不费有心人,我发现了 Feign 预留的自定义编码器接口 QueryMapEncoder,框架提供了两个实现:

虽然这两个实现不能满足我的要求,但是只要稍加修改写一个自己的实现类就行了,于是我在 FieldQueryMapEncoder 的基础上修改,仅仅添加了一个方法,小改了一个方法就实现了功能。

原理:Feign 其实还是用 Map 进行的编码,编码方式也很简单,String 是 key,Object 是 value。最开始的方式就是用 Object 的 toString() 方法把参数编码,这也是为什么 Date 字段会变成一个默认的时间格式,因为 toString() 根本和 @DateTimeFormat 没有关系。而高版本使用编码器实现了对象传参,实际实际上是通过简单的反射获取对象的元数据,再放到 Map 中。

上面的原理都能从 @DateTimeFormat 的注释和编码器的源码中得到答案。

我们要做的就是自定义一个编码器,实现在元数据放入 Map 之前根据需要把字段变成我们想要的字符串。下面是我实现的代码,供参考:

加注释的方法,就是我后添加进去的。encode 方法的最后一行稍微修改了一下,引用了我加的方法,其他都是直接借鉴过来的(本来我想更偷懒,直接继承一下子,但是它用了私有的内部类导致我只能全部复制粘贴了)。

以上就是关于openFeign各个参数详解全部的内容,包括:openFeign各个参数详解、Feign踩坑记录:JSON parse error、Feign源码解析二等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/10089340.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-05
下一篇 2023-05-05

发表评论

登录后才能评论

评论列表(0条)

保存