前言

feign 是目前微服务间通信的主流方式,是springCloud中一个非常重要的组件。他涉及到了负载均衡、限流等组件。真正意义上掌握了feign可以说就掌握了微服务。

一、feign的使用

feign 的使用和dubbo的使用本质上非常相似。dubbo的理念是:像调用本地方法一样调用远程方法。那么套在feign上同样适用:像调用本地接口一样调用远程接口。

使用feign只需要2步:定义一个接口并用FeignClient注解说明接口所在服务和路径,服务启动类上添加@EnableFeignClients。如下所示

1.1,定义一个feign接口

@FeignClient(contextId = "order", name = "order", path = "/app")
public interface OrderApiFeignClient { /**
* 获取订单列表
* @return
*/
@RequestMapping("order/list")
BaseResponse<List<OrderVO>> obtaining(@PathVariable("userId") Long userId);
}

1.2,再启动类上添加注解


@EnableSwagger2
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients("com.xxx.*")
@ComponentScan(value={"com.xxx"})
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication .class, args);
}
}

二、feign 接口如何被实例化到spring容器的?

首先按照一般的思路,我们会猜测基于接口生成代理类,然后对接口的调用实际上调的是代理对象,那真的是这样么? 我们带着猜想往下看。

2.1 @EnableFeignClients 注解都做了些什么?

可以看到注解本身主要定义了要扫描的feign接口包路径以及配置,但是注解本身又有注解Import ,可以看到他引入了FeignClientsRegistrar到容器。从名字看这个类就应该是在将feign接口注册到容器中,接下来我们具体看一下这个类干了些什么。

/**
* @author Spencer Gibb
* @author Jakub Narloch
* @author Venil Noronha
* @author Gang Li
*/
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

可以看到FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口,但凡是实现了这个接口的类被注入到容器后,spring容器在启用过程中都会去调用它的void registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2)方法,可以确定的是FeignClientsRegistrar肯定重写了此方法,我们接下来看一下该方法的实现。

可以看到在这个方法中做了两件事: 1)注册feign配置, 2)注册feign接口。我们这里抓一下重点,看一下feign接口是怎么注册的?


public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader); Set<String> basePackages; Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
// 限定只扫描FeingClient注解
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
else {
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
basePackages.add(ClassUtils.getPackageName(clazz));
clientClasses.add(clazz.getCanonicalName());
}
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
return clientClasses.contains(cleaned);
}
};
scanner.addIncludeFilter(
new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
} for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface"); Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName()); String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// 这里生成bean并且注册到容器
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}

上面这段代码概括起来就是: 先找了包路径basePackages , 然后在从这些包路径中查找带有FeignClient注解的接口,最后将注解的信息解析出来作为属性手动构建beanDefine注入到容器中。(这里有一个类ClassPathScanningCandidateComponentProvider,它可以根据filter扫描指定包下面的class对象,十分好用,建议收藏)。包路径的获取以及扫描feign相对简单,这里不做阐述,我们看一下它生成bean的过程,关注上面代码中的registerFeignClient方法。


private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
// 这里省略部分代码
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

代码中通过BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class)生成的BeanDefine(请记住这里设置得FeignClientFactoryBean.type就是feign接口对应得class对象)。那么所有的feign接口最终注册到容器中的都是FeignClientFactoryBean对应的一个实例(注意实际上注册到容器中压根就不是FeignClientFactoryBean对应的实例化对象,具体原因看下文),到此feign接口对应的实例注册过程已经完成。那么回到一开始的问题为什么我们调用接口的方法最终发起了请求? 是否有代理类的生成呢? 我们接下来看看FeignClientFactoryBean类的特殊之处

2.2 FeignClientFactoryBean 类,feign接口代理生成类

由上文知,每一个feign接口实际上最终都会生成FeignClientFactoryBean ,最终由FeignClientFactoryBean生成具体的bean实例注册到容器中。


/**
* @author Spencer Gibb
* @author Venil Noronha
* @author Eko Kurniawan Khannedy
* @author Gregor Zurowski
*/
class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware

可以看到该类实现了FactoryBean接口,这意味着当Spring注册该bean实例到容器中时,实际是调用其getObject方法,那么FeignClientFactoryBean一定是重写了getObject()方法,接下来我们看一下getObject()干了什么事情:

	public Object getObject() throws Exception {
return getTarget();
}

我们继续追踪getTarget()方法:

	<T> T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context); // 省略部分代码... Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}

显然最终的bean是通过target.target()方法生成,我们继续往下看:

	@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
return feign.target(target);
}

显然最终的bean是通过feign.target(target)生成。我们继续往下看:

    public <T> T target(Target<T> target) {
return build().newInstance(target);
}

显然最终得bean是通过build().newInstance(target)生成。我们继续往下看:

  public <T> T newInstance(Target<T> target) {
// 省略部分代码
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler); for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}

可以看到Proxy.newProxyInstance这个熟悉得身影了,没错他就是基于JDK原生得动态代理生成了FeignClientFactoryBean.type属性对应得class对应得代理类。从前文我们知道FeignClientFactoryBean.type就是feign接口得class对象。所以最终我们调用feign接口得方法实际上调用得是InvocationHandler方法。

三、小结

总结起来,就是常常我们挂在口头的东西就是将feign接口生成代理类,然后调用代理接口方法其实调用的代理类得方法,具体是为什么?不知道大家是否清楚。希望同福哦本文的阅读能让大家阅读源码的能力得到提升,也不在对feign有一种黑盒子的感觉。可能篇幅看起来较少,其实feign的注册过程牵涉到框架层面的知识还是蛮多的,包括springIoc、BeanDefine、动态代理等等,仔细看明白的话收获应该还是有蛮多的。哈哈,懂得都懂。顺手提一句:读源码一定要对SPI等等特别熟悉,要不然你会无从下手,没有方向,抓不到重点。后续会更新文章讲feign怎么实现负载均衡、熔断等。

(本文原创、转载请注明出处)

微服务通信之feign的注册、发现过程的更多相关文章

  1. 微服务通信之feign集成负载均衡

    前言 书接上文,feign接口是如何注册到容器想必已然清楚,现在我们着重关心一个问题,feign调用服务的时候是如何抉择的?上一篇主要是从读源码的角度入手,后续将会逐步从软件构架方面进行剖析. 一.R ...

  2. 微服务通信之feign的配置隔离

    前言 由上文我们知道针对某一个Feign接口,我们可以给他设置特定的配置类.那如果现在有一个服务,我们只想对A服务配置一个拦截器拦截请求而不影响其他服务,那应该怎么做呢? 一.feign接口配置 由前 ...

  3. 如何将 Redis 用于微服务通信的事件存储

    来源:Redislabs作者:Martin Forstner 翻译:Kevin (公众号:中间件小哥) 以我的经验,将某些应用拆分成更小的.松耦合的.可协同工作的独立逻辑业务服务会更易于构建和维护.这 ...

  4. 【SpringCloud构建微服务系列】Feign的使用详解

    一.简介 在微服务中,服务消费者需要请求服务生产者的接口进行消费,可以使用SpringBoot自带的RestTemplate或者HttpClient实现,但是都过于麻烦. 这时,就可以使用Feign了 ...

  5. java架构之路-(微服务专题)feign的基本使用和nacos的配置中心

    上次回归: 上次我们说了ribbon的基本使用,包括里面的内部算法,算法的细粒度配置,还有我们自己如何实现我们自己的算法,主要还是一些基本使用的知识,还不会使用ribbon的小伙伴可以回去看一下上一篇 ...

  6. 微服务通信之ribbon实现原理

    前言 上一篇我们知道了feign调用实现负载均衡是通过集成ribbon实现的.也较为详细的了解到了集成的过程.现在我们看一下ribbo是如何实现负载均衡的.写到这里我尚未去阅读源代码,我在这里盲猜一下 ...

  7. SpringCloud微服务(基于Eureka+Feign+Hystrix+Zuul)

    一.搭建注册中心 1.1.创建一个cloud-service项目 1.2:POM文件依赖 1 <?xml version="1.0" encoding="UTF-8 ...

  8. 微服务(三) Eureka注册中心和Ribbon负载均衡

    1. Eureka注册中心 1.1 Eureka的结构和作用 在上一篇文章中 微服务(二)服务拆分及远程调用 order-service在发起远程调用的时候,该如何得知user-service实例的i ...

  9. .netcore 微服务快速开发框架 Anno&Viper 注册中心 (服务上线下线预警通知)

    1.微服务时代,服务上线先预警通知 在微服务大行其道的今天,相信很多人都用上了微服务或者是微服务的概念也已经有了一个深刻的了解.今天我们不在这里展开阐述,今天我们要说的是微服务伴侣预警通知. 2.注册 ...

随机推荐

  1. nodejs打包成桌面程序(exe)的进阶之路

    nodejs打包成桌面程序(exe)的进阶之路 node js bat 前端 计划任务 前言:最近的研究,请大佬们细品 第一篇 - 任务计划程序篇 说真的研究到将nodejs打包成可执行的exe文件是 ...

  2. PyTorch迁移学习-私人数据集上的蚂蚁蜜蜂分类

    迁移学习的两个主要场景 微调CNN:使用预训练的网络来初始化自己的网络,而不是随机初始化,然后训练即可 将CNN看成固定的特征提取器:固定前面的层,重写最后的全连接层,只有这个新的层会被训练 下面修改 ...

  3. 手写Promise看着一篇就足够了

    目录 概要 博客思路 API的特性与手写源码 构造函数 then catch Promise.resolved Promise.rejected Promise.all Promise.race 概要 ...

  4. 【学习中】Unity插件之NGUI 完整视频教程

    课程 章节 内容 签到 Unity插件之NGUI 完整视频教程 第一章 NGUI基础控件和基础功能学习 1.NGUI介绍和插件的导入 6月29日 2.创建UIRoot 6月29日 3.学习Label控 ...

  5. CentOS yum 安装时错误 Errno 14 Couldn't resolve host 解决办法

    在虚拟机上安装完CentOS6.5之后,首次使用时yum命令安装软件时,出现一堆的” Errno 14 Couldn't resolve host”这个问题. 上网上查了半天,很多都说在/etc/re ...

  6. JS 进制转换的理解

    该事情的由来是来自于一个面试题,题目是这样的,[1,2,3].map(parseInt)的结果是什么? 作为菜鸟的我们一定是觉得分别把1,2,3分别交给parseInt,无非就是1,2,3嘛.其实结果 ...

  7. Oracle中创建千万级大表归纳

    从一月至今,我总共归纳了三种创建千万级大表的方案,它们是: 下面是这三种方案的对比表格: # 名称 地址 主要机制 速度 1 在Oracle中十分钟内创建一张千万级别的表 https://www.cn ...

  8. 原生 Java 客户端进行消息通信

    原生 Java 客户端进行消息通信 Direct 交换器 DirectProducer:direct类型交换器的生产者 NormalConsumer:普通的消费者 MulitBindConsumer: ...

  9. 喵的Unity游戏开发之路 - 多场景:场景加载

    如果丢失格式.图片或视频,请查看原文:https://mp.weixin.qq.com/s/RDVMg6l41uc2IHBsscc0cQ 很多童鞋没有系统的Unity3D游戏开发基础,也不知道从何开始 ...

  10. tcp建立连接为什么需要三次握手和四次挥手

    前言 众所周知tcp传输层协议在建立连接的时候需要三次才能建立起一个真正的可靠连接,可是为什么是三次呢,不可以是两次,四次等等呢,可以自己思考一番,带着疑问可以看下文. 三次握手 在<计算机网络 ...