OpenFeign 远程调用流程及超时时间源码解析
OpenFeign为什么只需要写一个接口就能远程调用? 是怎么生成代理类的 整个流程又是怎么样的?
为什么使用openFeign时,超时时间是一秒钟? 而有时候超时时间又是60秒钟 ?
不知道你会不会有这样的困惑 如果有的话,或许这篇博文能给你答案
1.添加测试案例
1.1 服务提供者
controller
@RestController public class HelloController { @GetMapping("/say") public String say() { return "Hello World"; } }
主启动类
@SpringBootApplication public class ProviderApp { public static void main(String[] args) { SpringApplication.run(ProviderApp.class); } }
配置信息
server: port: 9000 spring: application: name: provider
1.2 open接口
@FeignClient(name = "providerFeign",url = "http://localhost:9000") public interface ProviderFeign { @GetMapping("/say") public String say(); }
1.3 OpenFeign调用者
主启动类
@EnableFeignClients(basePackages = "com.sgg.feign") @SpringBootApplication public class ConsumerApp { public static void main(String[] args) { SpringApplication.run(ConsumerApp.class); } }
配置信息
server: port: 10000 spring: application: name: consumer
测试类
@SpringBootTest public class ConsumerTest { @Autowired private ProviderFeign providerFeign; @Test public void feignTest(){ String say = providerFeign.say(); System.out.println("say = " + say); } }
controller
@RestController public class ConsumerController { @Autowired private ProviderFeign providerFeign; @GetMapping("/say") public String say() { return providerFeign.say(); } }
2.OpenFeign 超时时间测试
1.1 OpenFeign的超时时间为60秒的情况
相信刚学SpringCloud OpenFeign的时候,你可能听说过 远程调用默认超时时间时一秒钟,超过一秒钟就会抛出异常
那好 我现在演示一个例子给你看
远程调用say 中间我让它睡10秒钟
这是测试类
这是Feign接口
远程调用成功,并没有抛出超时异常
到底超时时间时多久呢 我们改下服务业务代码 睡200秒 看下到底超时时间是多久
结果输出60秒
1.2 60秒超时时间源码浅析
DEBUG模式启动 跟进
进入 invoke(Object proxy, Method method, Object[] args) 方法
首先是 三个判断 当前是否是调用 Object的 equals hashCode toString 方法
如果都不是 调用 SynchronousMethodHandler 的 invoke方法 并把参数传过去 我们这里feign接口没有传任何参数 所以 args是null
首先创建了一个RequestTemplate模板对象
我们看下这个template对象的属性
封装了请求相关的参数信息 比如请求方式url地址 请求体等等
注意一个点 openfeign会丢失请求头 原因就是创建RequestTemplate模板对象的时候就没有封装请求头数据
跟进方法 executeAndDecode(RequestTemplate template, Options options)
我们跟进下 创建Request 的 targetRequest(template) 方法
遍历请求拦截器,执行拦截器的apply方法
所有当我们需要在项目中使用openFeign时,又不想丢失请求头信息,可以创建一个requestInterceptor 拦截器 实现 RequestInterceptor接口,将请求头数据添加到 RequestTemplate模板中
继续看
看下这个方法是怎么拿到返回对象的
首先看下这个 提交请求的Client 对象 , 有四个实现类对象
我们这里是调用 Default实现类的方法
跟进 execute 方法
调用了本类的 convertAndSend 返回一个 Http连接对象
跟进 convertAndSend 方法
可以看到在这设置了连接参数信息 看下options封装的参数信息
在这设置的读取超时为60秒
2.1 OpenFeign的超时时间为1 秒的情况
之前我们的feign接口设置了url地址,通过url远程调用服务
现在我们引用nacos作为注册中心 改下feign接口
nacos注册中心的服务名作为基准地址
实际上就是一秒超时 这种统计时间不精确 但我们知道超时时间发生了变化就行了
2.2 1 秒超时时间源码浅析
再次debug
我们先看下模板对象
重点看 executeAndDecode(template, options);
注意这时 options的连接超时还是10秒 读取超时是60秒
跟进方法
可以看到此时是 LoadBalancerFeignClient 实现类调用 execute() 方法
看下获取客户端配置的方法
getClientConfig(options, clientName)
看下这个getClientConfig(String name) 方法的返回结果
在这把超时时间改成了1秒 也就是1000毫秒
3. 对超时时间影响的小结论
@FeignClient的url属性是否设置值会影响Client到底是那个实现类对象 不同的实现类对象的Client的客户端配置是不一样的
默认的是60秒超时,但当是 LoadBalancerFeignClient 实现类时,Client的默认超时时间时1秒
4 .Feign接口是怎么被实例化到容器的
使用openFeign时,要求我们在主启动类上添加 @EnableFeignClients 注解 ,并且指定feign接口所在的包
那我们就来看下这个 @EnableFeignClients 注解干了什么事情
导入了一个组件 FeignClientsRegistrar Feign客户端的注册器
实现了三个接口 来动态的注册bean
ResourceLoaderAware
可以拿到资源加载器
EnvironmentAware
获取环境变量与配置文件的属性
看下动态注册bean的 ImportBeanDefinitionRegistrar接口 的 registerBeanDefinitions 方法
两个参数
AnnotationMetadata metadata //类的注解元信息
BeanDefinitionRegistry registry // bean定义信息注册器
两个方法
registerDefaultConfiguration(metadata, registry) // 注册Feign默认的配置信息
registerFeignClients(metadata, registry) //注册所有的feign客户端
首先看下这个 registerDefaultConfiguration(metadata, registry) 是怎么注册 Feign的配置信息的
首先拿到@EnableFeignClients 注册的属性与属性值
再判断是不是有外部类,有的话name值为 "default."+外部类的全限定类名
没有的话为 "default."+当前类的全限定类名
此时name 为 default.com.sgg.ConsumerApp
然后再调用 registerClientConfiguration 方法
registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
看下这个bean的定义信息
org.springframework.cloud.openfeign.FeignClientSpecification
回到 ImportBeanDefinitionRegistrar接口 的 registerBeanDefinitions 方法
接下来看下 registerFeignClients(metadata, registry) 方法是怎么注册feign客户端的
然后遍历所有的basePackages
将符合条件的BeanDefinition添加到容器中
注册客户端环境
registerClientConfiguration(registry, name, attributes.get("configuration"));
直接看注册feign客户端对象的方法
registerFeignClient(registry, annotationMetadata, attributes);
这都是在给beanDefinition 设置属性值
简单看下 生成的 beanDefinition
我们看下这个FeignClientFactoryBean
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware
实现了 FactoryBean<object> 接口 ,就去看他的getObject()方法</object>
调用了本类的getTarget()方法
看下这个loadBalance方法
之前我们已经知道Client是个接口 有四个实现类
看下这里返回的是那个实现类对象
跟进getOptional(context, Client.class);方法
实例一个LoadBalancerFeignClient对象 到这明白了为什么没有设置url属性值,实例的是LoadBalancerFeignClient对象
设置Feign抽象类的内部抽象类Builder 的 client属性
最终生成的bean 由target方法生成 点进去
调用了feign.target方法
最终走到了 ReflectiveFeign 的 newInstance 方法
用JDK动态代理 生成了一个代理对象放到容器中
最终执行feign接口的方法都是执行代理类也就是 FeignInvocationHandler 的invoke方法
发送Http请求都是由client接口的实现类对象完成的
5.如何拿到不同的Client实现类对象
再回头看下 FeignClientFactoryBean.getTarget()方法
如果我们设置了@FeignClient注解的url属性值
执行下面的业务代码
看下这里设置的client
此时Client是Default实现类对象
这就验证了 @FeignClient的url属性是否设置值会影响Client到底是那个实现类对象 不同的实现类对象的Client的客户端配置是不一样的
通过之前的源码知道了调用feign接口的方***走代理类的invoke方法
我们现在看下 这个invoke方法
这个dispatch也就是一个map缓存
拿到缓存中的MethodHandler 对象,调用它的invoke方法
MethodHandler 是一个接口,有两个实现类对象 如果调用的是feign接口中的Default修饰的方法 将由 DefaultMethodHandler处理
我们看非 Default修饰的接口方法,由SynchronousMethodHandler处理
看下SynchronousMethodHandler的invoke方法
是不是就是之前超时时间源码浅析的时候看到的代码
之前的源码解析可以看到Client 是由 FeignClientFactoryBean.getOptional方法获取的
我们看下这个方法
它从context中获取的Client实例
context是 FeignContext 创建 feign 类实例的工厂。它为每个客户端名称创建一个 Spring ApplicationContext,并从那里提取它需要的 bean
那我们就看自动配置相关信息
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\ org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration,\ org.springframework.cloud.openfeign.FeignAutoConfiguration,\ org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\ org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration,\ org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration
有6个自动配置类
最终发现由@Import导入DefaultFeignLoadBalancedConfiguration再进行注入的
同时还能在 FeignRibbonClientAutoConfiguration 自动配置类看到 注入了一个 Request.Options
默认的读取超时时间60秒,连接超时10秒在这设置的
如果没没有设置url,会走LoadBalancerFeignClient的execute方法
看这个
IClientConfig requestConfig = getClientConfig(options, clientName);
点进去 getClientConfig(options, clientName)方法
看下这个 RibbonClientConfiguration 配置信息类
这个值会去读取配置文件的属性值
所以我们设置openFeign超时时间有两种方式
6.设置openFeign超时时间的两种方式
feign: client: config: default : //feign客户端名,default则对所有的客户端有效 connectTimeout: 5000 readTimeout: 5000 loggerLevel: FULL
ribbon: ConnectTimeout: 5000 ReadTimeout: 5000
7.openFeign源码总结
在主启动类上添加 @EnableFeignClients 注解 会扫描所有指定包下(basePackages = " ") 如果没有指定,就是主启动类所在包及其子包下,添加了@FeignClient 注解的接口通过jdk动态代理生成代理类,调用代理类的方***执行 ReflectiveFeign .invoke 方法
然后调用 SynchronousMethodHandler.invoke方法,发送http请求,获取响应对象
生成 feign client客户端的时候 在FeignClientFactoryBean.getTarget()方法中 ,如果没有设置url ,则生成的是 LoadBalancerFeignClient 负载均衡客户端
如果设置了url 则生成的是 Default默认客户端
两个客户端的默认读取时间是不一样的 LoadBalancerFeignClient 的读取时间时 1s 响应时间也是1秒\
可以看 LoadBalancerFeignClient. execute 方法中的 getClientConfig(options, clientName)
最终返回的client配置类对象是 RibbonClientConfiguration.ribbonClientConfig()方法返回的 IClientConfig 对象
如果是 Default 也就是Client 的内部实现类对象
则客户端配置类是 Request.Options 默认连接超时10秒 读取超时60秒
#Java开发##学习路径#