1.测试环境搭建:

1.1 架构图:

product服务提供一个接口:

order服务通过feign的方式来调用product的接口:

order服务需要引入依赖:

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

order服务对外的controller :

order服务主启动类:

2. 源码分析

          2.1: spring 是如何找到@FeignClient标注的接口?
             我们在order服务启动类中加了一个注解:@EnableFeignClients,点击该注解进入看看

    

由图可知,该注解向容器导入了FeignClientsRegistrar.class类,然后我们跟踪进入该类:

该类实现了ImportBeanDefinitionRegistrar接口,该接口有个向spring容器注册的组件的方法:

debug调试:发现metadata就是主启动类的信息

先跟踪第一个方法:registerDefaultConfiguration(metadata, registry);

上图:通过主启动类获取@EnableFeignClients注解的defaultConfiguration属性,同时创建了一个名叫name的变量,值为:default.+主启动类的全限定类名

继续跟进去:

上图:向sprin容器中注册了一个FeignClientSpecification类,beanName为:default.com.yang.xiao.hui.order.OrderApplication.FeignClientSpecification

@EnableFeignClients注解的defaultConfiguration属性我并没有配置信息,所以FeignClientSpecification的属性值是一个空的数组

第一个方法分析完后,下面分析第二个:

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()); //获取EnableFeignClients注解的元素据,因为该注解可以包含你要扫描的路径 AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class); //注解过滤器,代表要过滤含有FeignClient.class的类
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) { //由于我没有配置对应的属性,所以会走这个分支
scanner.addIncludeFilter(annotationTypeFilter); //扫描器将注解过滤器新增进来了,这个是重点了
basePackages = getBasePackages(metadata); //EnableFeignClients注解的value,basePackages,basePackageClasses属性有值,就扫描这些配置的包名,如果没设置就扫描主启动类的包名,此处我没有配置,所以这里得到的是主启动类的报名
}
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); //通过扫描器,读取包名下所有的类,然后看看哪些是有FeignClient.class注解的,将这些类转成beanDefinition对象
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) { //这些BeanDefinition 都含有FeignClient.class注解
// 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()); //获取FeignClient.class注解元信息 String name = getClientName(attributes);//获取FeignClient.class的name值,我这里配置的是@FeignClient(name ="product" ),所以name就是product
registerClientConfiguration(registry, name, attributes.get("configuration"));//这个跟之前分析的第一个方法逻辑是一样的:向容器注入了一个name=product.feignClientSpecification 类型feignClientSpecification的bean registerFeignClient(registry, annotationMetadata, attributes);//注册所有的带有@FeignClient(name ="product" )注解的bean
}
}
}
}

上面代码总结:主要做了:

1.创建了一个扫描器ClassPathScanningCandidateComponentProvider scanner,并将AnnotationTypeFilter过滤器传入扫描器中(scanner.addIncludeFilter(annotationTypeFilter)),用于过滤扫描到的带有@FeignClient注解的类

2.获取要扫描的包路径:basePackages = getBasePackages(metadata);

3.遍历包名获取候选的带有FeignClient注解的类的信息 Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);

因为扫描器之前加了一个注解过滤器,这里isCandidateComponent(metadataReader)的逻辑是使用注解过滤器来过滤出带有FeignClien注解的类

4.遍历扫描到的BeanDefinition,向ioc容器中注册对应的bean,这里每个带有@FeignClient的类都会注册2个bean

registerClientConfiguration(registry, name,attributes.get("configuration")); 向容器中注册了name=product.feignClientSpecification 类型feignClientSpecification的bean

重点是: registerFeignClient(registry, annotationMetadata, attributes);方法

所以,该方法向spring容器注入的bean,名字为:com.yang.xiao.hui.order.controller.ProductService,类型为FeignClientFactoryBean,而FactoryBean都会提供一个getObject方法获取对应的实例

spring如何获取带有@FeignClient注解标准的接口,总结如下图:

3.springboot自动装配机制:springboot启动时会加载类路径下/META-INF/spring.factories中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的类:

因此有四个相关的配置类会被加载,我们主要关注2个FeignRibbonClientAutoConfiguration 和FeignAutoConfiguration

//再看看DefaultFeignLoadBalancedConfiguration这个类:

之后看看这个bean: FeignAutoConfiguration

总结:自动装配机制注入的bean:

4.获取第三方服务实例

4.1由前面分析,每个带有@FeignClient注解的接口都会被注入到spring容器,名字为接口的权限定类名,类型为FeignClientFactoryBean

4.2我们debug调试,从容器中根据beanName获取对应的实例看看,我这里的beanName=com.yang.xiao.hui.order.controller.ProductService,order服务的controller方法先修改下:

      

跟进该方法:

对于factoryBean,如果beanName前面带上“&” 符号,获取的bean就是原始的factoryBean,如果没带该符号,获取的是factoryBean.getObject()方法返回的bean,我们这次调试是没带上该符合的

至此,我们可以知道,所有带有@FeignClient注解的接口获取实例,最终都是调用FeignClientFactoryBean的getObject方法,该方法才是我们的重点

4.3 FeignClientFactoryBean的getObject()方法的讲解:

分析: Feign.Builder builder = feign(context);

//上面很多bean都是通过get(Context,xxx.class)方法获取,而context就是FeignContext,在springBoot自动装配时注入了spring容器,那么我们跟踪第一个方法看看:

FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);

这里有2个疑问:这个容器是怎么创建的,为何能通过该容器获取对应的bean

由于上面的获取子容器的方法是在FeignContext中的,所以我们要来分析下FeignContext的创建过程:

FeignContext创建时调用了父类的有参构造

此时们再回到,通过服务名称获取子容器的方法,获取不到就创建一个子容器:

由图可知,子容器注入了一个FeignClientsConfiguration配置类,该类是由FeignContext初始化时,调用父类有参构造器传入的,所以分析下该配置类:

@Configuration(proxyBeanMethods = false)
public class FeignClientsConfiguration { @Autowired
private ObjectFactory<HttpMessageConverters> messageConverters; @Autowired(required = false)
private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>(); @Autowired(required = false)
private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>(); @Autowired(required = false)
private Logger logger; @Autowired(required = false)
private SpringDataWebProperties springDataWebProperties; @Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new OptionalDecoder(
new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
} @Bean
@ConditionalOnMissingBean
@ConditionalOnMissingClass("org.springframework.data.domain.Pageable")
public Encoder feignEncoder() {
return new SpringEncoder(this.messageConverters);
} @Bean
@ConditionalOnClass(name = "org.springframework.data.domain.Pageable")
@ConditionalOnMissingBean
public Encoder feignEncoderPageable() {
PageableSpringEncoder encoder = new PageableSpringEncoder(
new SpringEncoder(this.messageConverters));
if (springDataWebProperties != null) {
encoder.setPageParameter(
springDataWebProperties.getPageable().getPageParameter());
encoder.setSizeParameter(
springDataWebProperties.getPageable().getSizeParameter());
encoder.setSortParameter(
springDataWebProperties.getSort().getSortParameter());
}
return encoder;
} @Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
} @Bean
public FormattingConversionService feignConversionService() {
FormattingConversionService conversionService = new DefaultFormattingConversionService();
for (FeignFormatterRegistrar feignFormatterRegistrar : this.feignFormatterRegistrars) {
feignFormatterRegistrar.registerFormatters(conversionService);
}
return conversionService;
} @Bean
@ConditionalOnMissingBean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY;
} @Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
} @Bean
@ConditionalOnMissingBean(FeignLoggerFactory.class)
public FeignLoggerFactory feignLoggerFactory() {
return new DefaultFeignLoggerFactory(this.logger);
} @Bean
@ConditionalOnClass(name = "org.springframework.data.domain.Page")
public Module pageJacksonModule() {
return new PageJacksonModule();
} @Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration { @Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled") //这里可以发现,如果要想让hystrix生效,需要配置一下属性feign.hystrix.enabled
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
} } }

通过该配置类,可以知道,子容器中注入了Decoder,Encoder,Contract,FormattingConversionService,Retryer,FeignLoggerFactory,Module

此时我们再看看父容器和子容器注入的主要类:

有了上面的分析,我们回到之前的方法继续分析:

我们看到该方法:configureUsingProperties(properties.getConfig().get(this.contextId),builder);,通过一个ContextId也就是服务名,我这里是product,获取该服务的专有配置,说明每一个服务都可以拥有自己的特殊配置;
看看这个FeignClientProperties类:

我们再次回到FactoryBean的getObject()方法进行分析:

上图,我们看到从容器中获取了一个client和targeter,这2个bean是在父容器中获取的,前面有分析过了:

继续跟进:

可见是调用了ReflectiveFeign的newInstance(target);方法

至此,我们可以发现,最终是调用了JDK动态代理机制来生成代理类,下面再来分析上面2个方法:

Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);和InvocationHandler handler = factory.create(target, methodToHandler);
先看第一个方法:targetToHandlersByName.apply(target)

我这里只有一个方法:

所以跟进去:

可见,校验是对RequestMaping注解的校验

我们分析对方法上的处理:

之后我们再分析InvocationHandler handler = factory.create(target, methodToHandler);方法:

对上述所有分析做个总结:

5.我们是如何通过feign调用到第三方服务的,在这里就是我们是如何通过order服务调用到product服务:

5.1:调用任何服务都要知道ip和端口,因此,如何获取ip和端口,如果服务提供者有多个,如何进行负载均衡,构建测试代码如下,在order服务下:

我们看看server是如何获取到的,这里用到ribbon进行负载均衡,点击command.submit

上图是ribbon获取负载均衡获取单个服务实例的过程,可以参考我之前的博客,有详细源码讲解:https://www.cnblogs.com/yangxiaohui227/p/12614343.html

继续跟踪,到达了:

最终是使用HttpURLConnection发起请求:

 

 

spring-cloud-starter-openfeign 源码详细讲解的更多相关文章

  1. Spring Cloud 学习 之 Spring Cloud Eureka(源码分析)

    Spring Cloud 学习 之 Spring Cloud Eureka(源码分析) Spring Boot版本:2.1.4.RELEASE Spring Cloud版本:Greenwich.SR1 ...

  2. Spring Cloud Netflix Eureka源码导读与原理分析

    Spring Cloud Netflix技术栈中,Eureka作为服务注册中心对整个微服务架构起着最核心的整合作用,因此对Eureka还是有很大的必要进行深入研究. 本文主要分为四部分,一是对项目构建 ...

  3. spring cloud集成 consul源码分析

    1.简介 1.1 Consul is a tool for service discovery and configuration. Consul is distributed, highly ava ...

  4. 微服务分布式 spring cloud springboot 框架源码 activiti工作流 前后分离

    1.代码生成器: [正反双向](单表.主表.明细表.树形表,快速开发利器)freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面.建表sql脚本.处理类.service等完整模块2. ...

  5. spring cloud springboot 框架源码 activiti工作流 前后分离 集成代码生成器

    1.代码生成器: [正反双向](单表.主表.明细表.树形表,快速开发利器)freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面.建表sql脚本.处理类.service等完整模块2. ...

  6. Feign 系列(05)Spring Cloud OpenFeign 源码解析

    Feign 系列(05)Spring Cloud OpenFeign 源码解析 [TOC] Spring Cloud 系列目录(https://www.cnblogs.com/binarylei/p/ ...

  7. spring boot 2.0 源码分析(四)

    在上一章的源码分析里,我们知道了spring boot 2.0中的环境是如何区分普通环境和web环境的,以及如何准备运行时环境和应用上下文的,今天我们继续分析一下run函数接下来又做了那些事情.先把r ...

  8. SpringMVC+Maven开发项目源码详细介绍

    代码地址如下:http://www.demodashi.com/demo/11638.html Spring MVC概述 Spring MVC框架是一个开源的Java平台,为开发强大的基于Java的W ...

  9. AQS源码详细解读

    AQS源码详细解读 目录 AQS源码详细解读 基础 CAS相关知识 通过标识位进行线程挂起的并发编程范式 MPSC队列的实现技巧 代码讲解 独占模式 独占模式下请求资源 独占模式下的释放资源 共享模式 ...

随机推荐

  1. python爬虫数据提取之bs4的使用方法

    Beautiful Soup的使用 1.下载 pip install bs4 pip install lxml # 解析器 官方推荐 2.引用方法 from bs4 import BeautifulS ...

  2. 归纳从文件中读取数据的六种方法-JAVA IO基础总结第2篇

    在上一篇文章中,我为大家介绍了<5种创建文件并写入文件数据的方法>,本节我们为大家来介绍6种从文件中读取数据的方法. 另外为了方便大家理解,我为这一篇文章录制了对应的视频:总结java从文 ...

  3. 单表千万行数据库 LIKE 搜索优化手记

    我们经常在数据库中使用 LIKE 操作符来完成对数据的模糊搜索,LIKE 操作符用于在 WHERE 子句中搜索列中的指定模式. 如果需要查找客户表中所有姓氏是“张”的数据,可以使用下面的 SQL 语句 ...

  4. Centos7安装Oracle12c教程

    12c数据库 创建oracle的系统用户和用户组 [root@localhost /]# groupadd oinstall [root@localhost /]# groupadd dba [roo ...

  5. 腾讯大牛半年心血高级编程PDF,帮你轻松构建企业级Web应用

    毫无疑问,Java 是这些年来最流行的编程语言之一.它无处不在一计算机. 手机.网站以及各种嵌入式设备中都存在着大量的Java 应用程序,而其中应用最为广泛的应该就是Java EE Web应用程序(以 ...

  6. Tesselation学习

    Tesselation的作用:给低片面数模型镶嵌更多片面,让低模变高模. 和法线贴图不同,法线本质是通过改变低模表面的颜色来模拟高模,比如在一个片面上普通diffuse是均匀的颜色分布(因为光照颜色一 ...

  7. 喵的Unity游戏开发之路 - 互动环境(有影响的运动)

    如图片.视频或代码格式等显示异常,请查看原文: https://mp.weixin.qq.com/s/Sv0FOxZCAHHUQPjT8rUeNw 很多童鞋没有系统的Unity3D游戏开发基础,也不知 ...

  8. 【jmespath】—1. 基础用法

    一.jsonpath 之前我写接口自动化测试时候,对于复杂的json返回,会使用jsonpath这个第三方库,就像写xpath一样,方便的查询json元素. 因为之前写WEB自动化时候,总用xpath ...

  9. C#通过Com串口进行Barcode Printer

    前言 工作中有遇到Barcode打印的需求,最开始是通过打印机型号找到对应的打印机,再进行操作,但是需要匹配的打印机型号太多,而且不定,所以处理起来太过麻烦. 后面通过找到通过串口找到打印机,直接传输 ...

  10. element封装表格

    <template> <div> <el-scrollbar class="table-wrap"> <el-table v-loadin ...