前言
openfeign在使用层面比较简单(几个注解就搞定了),底层逻辑依赖了Feign, Spring ,SpringCloud 本文尝试理下spring-cloud-openfeign底层运行机制(这样才能记得更牢啊 哈哈哈),有问题的地方大家也可以指出来
一、Demo
首先基于RuoYi框架来做个例子
这样就完成了一个通过OpenFeign来实现服务间调用的简单Demo,也是可以尝试自己去搭一个微服务项目架构的 但是不了解框架版本之间的依赖,RuoYi已经帮我们做好了这一步,直接基于框架来做就好了
接下来我们稍微想点问题,方便后面去研究OpenFeign底层做了什么
二、Openfeign注册Bean的逻辑
首先要看看项目引入的OpenFeign相关的包,这里我也把对应的源码给下下来了
FeignAutoConfiguration 这个自动配置文件主要实现来向Spring容器中注册Bean的功能
像这里的Capability 、Targeter、Client 都是Feign里面的概念 Feign框架最终通过Client发起服务间的调用
这里我们注意下这个配置文件导入的两个类 FeignClientSpecification, FeignContext
1、FeignContext
顾名思义Feign上下文,这里面应该存了Feign相关的组件,FeignContext 父类是NamedContextFactory,构造函数传入的FeignClientsConfiguration定义了一些Feign相关的组件
这里的getInstance方法第一个入参是contextName,在实际开发过程中过程中这个指的就是FeignClient注解的contextId字段了,OpenFeign实际就是针对每个FeignClient注解的接口都有一个自己的Spring环境,由FeignContext来管理,注入的Bean一部分是由默认的配置类FeignClientsConfiguration导入,另外一部分是由FeignClient注解的configuration字段导入
2、FeignClientSpecification
FeignClientSpecification 实现了NamedContextFactory.Specification 接口,这个接口是提供了一个获取配置类的接口方法getConfiguration 返回类型是Class,配置类(一般都是@Configuration注解类)引入一些Bean到容器
在FeignAutoConfiguration配置类中注入了一个FeignClientSpecification集合,这个集合作为FeignContext的参数
3、EnableFeignClients注解
在项目中使用OpenFeign首先要加上这个注解,我们知道在SpringBoot项目中,Enable开头的注解一般都是用来向容器中注入Bean的,接下来我们看看EnableFeignClients这个注解做了什么操作
这个注解又通过Import注解导入了一个FeignClientsRegistrar,熟悉Spring容器启动过程的同学可能知道,在容器启动过程中有ConfigurationClassPostProcessor这个类,他实现了BeanFactoryPostProcessor接口,容器Refresh过程中会处理Import注解导入的类,ConfigurationClassPostProcessor具体工作原理怎么样的,大家可以找其他资料看看过程比较复杂,我们直接看看FeignClientsRegistrar做了什么操作吧,具体的逻辑在registerBeanDefinitions方法里面
这里就看出来,项目中有多少个FeignClient注解就往容器中注册了多少个Class是FeignClientSpecification的Bean(注意FeignClient注解中的ContextId不要重复),这也就是为什么FeignAutoConfiguration要注入一个FeignClientSpecification集合了,我们再回过头来看FeignContext
接下来我们看registerFeignClient的代码,这里直接把源码贴出来,在FeignClientsRegistrar类下面
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
Class clazz = ClassUtils.resolveClassName(className, null);
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
String contextId = getContextId(beanFactory, attributes);
String name = getName(attributes); // serviceId
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
// bean定义有 Supplier 接口 通过Supplier创建bean对象 spring中按类型查找 有对应的Supplier 接口通过Supplier获取bean
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(clazz, () -> {
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
factoryBean.setDecode404(Boolean
.parseBoolean(String.valueOf(attributes.get("decode404"))));
Object fallback = attributes.get("fallback");
if (fallback != null) {
factoryBean.setFallback(fallback instanceof Class
? (Class<?>) fallback
: ClassUtils.resolveClassName(fallback.toString(), null));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
factoryBean.setFallbackFactory(fallbackFactory instanceof Class
? (Class<?>) fallbackFactory
: ClassUtils.resolveClassName(fallbackFactory.toString(),
null));
}
return factoryBean.getObject();
});
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
definition.setLazyInit(true);
validate(attributes);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String[] qualifiers = getQualifiers(attributes);
if (ObjectUtils.isEmpty(qualifiers)) {
qualifiers = new String[] { contextId + "FeignClient" };
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
这个方法在定义Bean的时候,传入了一个函数式接口Supplier
也就是我们在调用有FeignClient注解接口的方法时,从Spring容器中拿到的是FeignClientFactoryBean getObject方法返回的对象,看源码我们发现这个getObject方法底层最终也还是通过Feign框架的target方法返回了一个代理对象,只不过,构建配置Feign详细参数的过程中依赖了FeignContext注入的组件,前面也说了有两个地方向FeignContext中注入组件,一个是FeignClientsConfiguration类,另外也可以通过@FeignClient 注解configuration字段导入配置类(这里重新再提下。。。)
4、FeignClientFactoryBean
FeignClient注解有个URL字段,这个字段对应了FeignClientFactoryBean的URL字段,这个字段如果配置了值的话就是直连对应的URL,不用通过对应的服务名来找服务地址了,所以在FeignClientFactoryBean getTarget获取代理对象的逻辑里面还是做了区分的(配置URL地址的话不用再通过SpringCloudCommon去查找服务实例 )
了解Feign框架的朋友应该知道,Feign框架Targeter类target方法生成的代理对象最终都是通过Client(Feign框架的接口)发起服务间调用,具体Client有可以扩展Feign框架实现
在OpenFeign框架中 FeignAutoConfiguration FeignLoadBalancerAutoConfiguration 都有定义相关的Bean
在FeignAutoConfiguration配置类中 ,定义Class类型是Client的Bean一般都有个条件那就是对应的Bean不存在
而FeignLoadBalancerAutoConfiguration是先于FeignAutoConfiguration被解析的,在FeignLoadBalancerAutoConfiguration配置类解析过程中已经注册过相关的Bean了
在FeignLoadBalancerAutoConfiguration配置类中具体又引入了四个配置类,
我么先看看其中一个HttpClientFeignLoadBalancerConfiguration
DefaultFeignLoadBalancerConfiguration 这个配置类看名字就猜测它就是默认的配置类了,
这几个配置类导入的Bean都是实现了Feign框架的Client接口,同时依赖了LoadBalancerClient(或者LoadBalancedRetryFactory),LoadBalancerClientFactory 这都是spring-cloud-commons-parent里面的定义的接口,一般SpringCloud项目都引入了spring-cloud-commons
OpenFeign这里引入的Client 具体执行的时候就是交给负载均衡客户端去查找一个对应的服务实例出来,spring-cloud-commons 已经提供了基本的逻辑去筛选服务实例,就看项目中提供了什么DiscoveryClient了
5、DefaultFeignLoadBalancerConfiguration
这里引入了一个Class类型是FeignBlockingLoadBalancerClient的Bean(OpenFeign最终也是通过这个类执行最后的RPC调用) ,FeignBlockingLoadBalancerClient 注入了一个BlockingLoadBalancerClient(这个是Spring Cloud Common里面的概念),通过这个Client去查找一个服务实例,
BlockingLoadBalancerClient在BlockingLoadBalancerClientAutoConfiguration配置类中注入
继续跟踪代码171行获取到的loadBalance 是ReactorServiceInstanceLoadBalancer ,这个接口在Spring Cloud Common中有三种实现,我们看看在BlockingLoadBalancerClient注入的是哪个
只注册了 RoundRobinLoadBalancer 这个Bean,也就是通过 RoundRobinLoadBalancer的choose方法来查询出服务实例信息
RoundRobinLoadBalancer 依赖容器中的ServiceInstanceListSupplier查找服务实例
这个Bean在LoadBalancerClientConfiguration配置类中定义
在创建DiscoveryClientServiceInstanceListSupplier通过DiscoveryClient获取服务实例(也就是这个类在创建时已经获取到所有实例了 后面的get方法直接返回实例集合给调用方)
现在就看看DiscoveryClient(要明确注入的DiscoveryClient是什么)是怎样获取服务实例的了
在CompositeDiscoveryClientAutoConfiguration 配置类中注册了DiscoveryClient