前言

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. Nginx反向代理的使用

    一.Nginx的基本命令 nginx:启动 nginx nginx -t :测试配置文件是否有语法错误 nginx -s reopen:重启Nginx nginx -s reload:重新加载Ngin ...

  2. Java面试题(MySQL篇)

    MySql 164.数据库的三范式是什么? 第一范式:强调的是列的原子性,即数据库表的每一列都是不可分割的原子数据项. 第二范式:要求实体的属性完全依赖于主关键字.所谓完全依赖是指不能存在仅依赖主关键 ...

  3. 【学习中】Fitness Schedule

    章节 内容 签到 第1周:步伐 √第1课(34分钟) 跑步1分钟 行走2分钟 共做8次 4月24日 √第2课(28分钟) 跑步1分钟 行走2分钟 共做6次 4月27日 √第3课(31分钟) 跑步1分钟 ...

  4. [PyTorch 学习笔记] 5.1 TensorBoard 介绍

    本章代码: https://github.com/zhangxiann/PyTorch_Practice/blob/master/lesson5/tensorboard_methods.py http ...

  5. JVM学习第三天(JVM的执行子系统)之开篇Class类文件结构

    虽然这几天 很忙,但是学习是不能落下的,也不能推迟,因为如果推迟了一次,那么就会有无数次;加油,come on! Java跨平台的基础: 各种不同平台的虚拟机与所有平台都统一使用的程序存储格式——字节 ...

  6. shell知识点:${} 的神奇用法

    为了完整起见,我这里再用一些例子加以说明 ${ } 的一些特异功能:假设我们定义了一个变量为:file=/dir1/dir2/dir3/my.file.txt我们可以用 ${ } 分别替换获得不同的值 ...

  7. Nginx之rewrite使用

    rewrite regex replacement [flag];  flag=[break/last/redirect/permanent] ² regex 是正则表达式 ² replacement ...

  8. wfuzz 安装

    wfuzz 安装 win10 下的wfuzz安装 fuzz下载 https://github.com/xmendez/wfuzz 安装遇到的问题 0x1报错 解决方法: 更新pip python -m ...

  9. Linux:apache安装

    1.查询是否已安装 rpm -qa httpd 如果已安装,先卸载 发现有依赖包,先把依赖卸载 或者加上--nodeps参数,不考虑依赖,直接卸载   rpm -e --nodeps httpd-2. ...

  10. 说说XcodeLLDB调试的那些事儿

    使用场景之一,接收他人的项目,快速理清其层次结构,可以打标识符断点,如下图 每一个VC,都加了个在viewDidLoad方法处的断点,这样运行程序时,逐步断点,便可以理清层次, 但是,需要手动不断的继 ...