前言

前情回顾

上一讲深入的讲解了Ribbon的初始化过程及Ribbon与Eureka的整合代码,与Eureka整合的类就是DiscoveryEnableNIWSServerList,同时在DynamicServerListLoadBalancer中会调用PollingServerListUpdater 进行定时更新Eureka注册表信息到BaseLoadBalancer中,默认30s调度一次。

本讲目录

这一讲主要是讲Fegin Demo以及通过入口注解@EnableFeignCliets和@FeignClient来进行源码初探。

目录如下:

  1. Feign代码Demo
  2. Feign调用原理
  3. @EnableEurekaClient和@FeignClient注解扫描

说明

原创不易,如若转载 请标明来源!

博客地址:一枝花算不算浪漫

微信公众号:壹枝花算不算浪漫

源码分析

Feign代码Demo

Fegin的Demo还是延续之前讲解的Eureka的代码。地址为:

https://github.com/barrywangmeng/spring-cloud-learn



如上图所示,ServiceB调用ServiceA的服务,定义了一个@FeignClient标注的ServiceAFeignClient接口,里面定义了ServiceA中Controller提供的接口信息。

Feign调用原理

接着我们可以启动所有服务,调用ServiceBController,然后在serviceAFeignClient处打上断点看一下:

我们发现这里serviceAFeignClient显示的是:ReflectiveFeign$FeignInvocationHandler@7642 ,这里其实是使用了动态代理,因为ServiceAFeignClient 是一个接口,所以这里可以猜测到底层使用的是JDK动态代理。

接着可以简单地梳理下Feign请求简单原理图:

@EnableEurekaClient和@FeignClient注解扫描

先看下@EnableFeignClients注解代码:

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.TYPE)
  3. @Documented
  4. @Import(FeignClientsRegistrar.class)
  5. public @interface EnableFeignClients {
  6. //等价于basePackages属性,更简洁的方式
  7. String[] value() default {};
  8. //指定多个包名进行扫描
  9. String[] basePackages() default {};
  10. //指定多个类或接口的class,扫描时会在这些指定的类和接口所属的包进行扫描
  11. Class<?>[] basePackageClasses() default {};
  12. //为所有的Feign Client设置默认配置类
  13. Class<?>[] defaultConfiguration() default {};
  14. //指定用@FeignClient注释的类列表。如果该项配置不为空,则不会进行类路径扫描
  15. Class<?>[] clients() default {};
  16. }

接着看下@FeignClient注解代码:

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface FeignClient {
  5. //指定Feign Client的名称,如果项目使用了 Ribbon,name属性会作为微服务的名称,用于服务发现
  6. @AliasFor("name")
  7. String value() default "";
  8. //用serviceId做服务发现已经被废弃,所以不推荐使用该配置
  9. @Deprecated
  10. String serviceId() default "";
  11. //指定Feign Client的serviceId,如果项目使用了 Ribbon,将使用serviceId用于服务发现,但上面可以看到serviceId做服务发现已经被废弃,所以也不推荐使用该配置
  12. @AliasFor("value")
  13. String name() default "";
  14. //为Feign Client 新增注解@Qualifier
  15. String qualifier() default "";
  16. //请求地址的绝对URL,或者解析的主机名
  17. String url() default "";
  18. //调用该feign client发生了常见的404错误时,是否调用decoder进行解码异常信息返回,否则抛出FeignException
  19. boolean decode404() default false;
  20. //Feign Client设置默认配置类
  21. Class<?>[] configuration() default {};
  22. //定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback 指定的类必须实现@FeignClient标记的接口。实现的法方法即对应接口的容错处理逻辑
  23. Class<?> fallback() default void.class;
  24. //工厂类,用于生成fallback 类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
  25. Class<?> fallbackFactory() default void.class;
  26. //定义当前FeignClient的所有方法映射加统一前缀
  27. String path() default "";
  28. //是否将此Feign代理标记为一个Primary Bean,默认为ture
  29. boolean primary() default true;
  30. }

接着我们看下@EnableFeignClients中注入的

@Import(FeignClientsRegistrar.class) 源码:

  1. class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
  2. ResourceLoaderAware, EnvironmentAware {
  3. // patterned after Spring Integration IntegrationComponentScanRegistrar
  4. // and RibbonClientsConfigurationRegistgrar
  5. private ResourceLoader resourceLoader;
  6. private Environment environment;
  7. public FeignClientsRegistrar() {
  8. }
  9. @Override
  10. public void setResourceLoader(ResourceLoader resourceLoader) {
  11. this.resourceLoader = resourceLoader;
  12. }
  13. //在这个重载的方法里面做了两件事情:
  14. //1.将EnableFeignClients注解对应的配置属性注入
  15. //2.将FeignClient注解对应的属性注入
  16. @Override
  17. public void registerBeanDefinitions(AnnotationMetadata metadata,
  18. BeanDefinitionRegistry registry) {
  19. //注入EnableFeignClients注解对应的配置属性
  20. registerDefaultConfiguration(metadata, registry);
  21. //注入FeignClient注解对应的属性
  22. registerFeignClients(metadata, registry);
  23. }
  24. /**
  25. * 拿到 EnableFeignClients注解 defaultConfiguration 字段的值
  26. * 然后进行注入
  27. *
  28. **/
  29. private void registerDefaultConfiguration(AnnotationMetadata metadata,
  30. BeanDefinitionRegistry registry) {
  31. Map<String, Object> defaultAttrs = metadata
  32. .getAnnotationAttributes(EnableFeignClients.class.getName(), true);
  33. if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
  34. String name;
  35. if (metadata.hasEnclosingClass()) {
  36. name = "default." + metadata.getEnclosingClassName();
  37. }
  38. else {
  39. name = "default." + metadata.getClassName();
  40. }
  41. registerClientConfiguration(registry, name,
  42. defaultAttrs.get("defaultConfiguration"));
  43. }
  44. }
  45. public void registerFeignClients(AnnotationMetadata metadata,
  46. BeanDefinitionRegistry registry) {
  47. // 获取ClassPath扫描器
  48. ClassPathScanningCandidateComponentProvider scanner = getScanner();
  49. // 为扫描器设置资源加载器
  50. scanner.setResourceLoader(this.resourceLoader);
  51. Set<String> basePackages;
  52. // 1. 从@EnableFeignClients注解中获取到配置的各个属性值
  53. // 这里可以获取到配置的:basePackages=com.barrywang.service.feign
  54. Map<String, Object> attrs = metadata
  55. .getAnnotationAttributes(EnableFeignClients.class.getName());
  56. // 2. 注解类型过滤器,只过滤@FeignClient
  57. AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
  58. FeignClient.class);
  59. // 3. 从1. 中的属性值中获取clients属性的值
  60. final Class<?>[] clients = attrs == null ? null
  61. : (Class<?>[]) attrs.get("clients");
  62. if (clients == null || clients.length == 0) {
  63. // 扫描器设置过滤器且获取需要扫描的基础包集合
  64. scanner.addIncludeFilter(annotationTypeFilter);
  65. basePackages = getBasePackages(metadata);
  66. }else {
  67. // clients属性值不为null,则将其clazz路径转为包路径
  68. final Set<String> clientClasses = new HashSet<>();
  69. basePackages = new HashSet<>();
  70. for (Class<?> clazz : clients) {
  71. basePackages.add(ClassUtils.getPackageName(clazz));
  72. clientClasses.add(clazz.getCanonicalName());
  73. }
  74. AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
  75. @Override
  76. protected boolean match(ClassMetadata metadata) {
  77. String cleaned = metadata.getClassName().replaceAll("\\$", ".");
  78. return clientClasses.contains(cleaned);
  79. }
  80. };
  81. scanner.addIncludeFilter(
  82. new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
  83. }
  84. // 3. 扫描基础包,且满足过滤条件下的接口封装成BeanDefinition
  85. for (String basePackage : basePackages) {
  86. // 找到basePackage下定义的@FeignClient接口列表
  87. Set<BeanDefinition> candidateComponents = scanner
  88. .findCandidateComponents(basePackage);
  89. // 遍历扫描到的bean定义
  90. for (BeanDefinition candidateComponent : candidateComponents) {
  91. if (candidateComponent instanceof AnnotatedBeanDefinition) {
  92. // 并校验扫描到的bean定义类是一个接口
  93. AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
  94. AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
  95. Assert.isTrue(annotationMetadata.isInterface(),
  96. "@FeignClient can only be specified on an interface");
  97. // 获取@FeignClient注解上的各个属性值
  98. Map<String, Object> attributes = annotationMetadata
  99. .getAnnotationAttributes(
  100. FeignClient.class.getCanonicalName());
  101. String name = getClientName(attributes);
  102. // 可以看到这里也注册了一个FeignClient的配置bean
  103. // 这个方法是创建了一个FeignClientFactoryBean的工厂类,里面保存@FeignClient注解的所有属性值
  104. registerClientConfiguration(registry, name,
  105. attributes.get("configuration"));
  106. // 注册bean定义到spring中
  107. registerFeignClient(registry, annotationMetadata, attributes);
  108. }
  109. }
  110. }
  111. }
  112. /**
  113. * 注册bean
  114. **/
  115. private void registerFeignClient(BeanDefinitionRegistry registry,
  116. AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
  117. // 1.获取类名称,也就是本例中的FeignService接口
  118. String className = annotationMetadata.getClassName();
  119. // 2.BeanDefinitionBuilder的主要作用就是构建一个AbstractBeanDefinition
  120. // AbstractBeanDefinition类最终被构建成一个BeanDefinitionHolder
  121. // 然后注册到Spring中
  122. // 注意:beanDefinition类为FeignClientFactoryBean,故在Spring获取类的时候实际返回的是
  123. // FeignClientFactoryBean类
  124. BeanDefinitionBuilder definition = BeanDefinitionBuilder
  125. .genericBeanDefinition(FeignClientFactoryBean.class);
  126. validate(attributes);
  127. // 3.添加FeignClientFactoryBean的属性,
  128. // 这些属性也都是我们在@FeignClient中定义的属性
  129. definition.addPropertyValue("url", getUrl(attributes));
  130. definition.addPropertyValue("path", getPath(attributes));
  131. String name = getName(attributes);
  132. definition.addPropertyValue("name", name);
  133. definition.addPropertyValue("type", className);
  134. definition.addPropertyValue("decode404", attributes.get("decode404"));
  135. definition.addPropertyValue("fallback", attributes.get("fallback"));
  136. definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
  137. definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
  138. // 4.设置别名 name就是我们在@FeignClient中定义的name属性
  139. String alias = name + "FeignClient";
  140. AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
  141. boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null
  142. beanDefinition.setPrimary(primary);
  143. String qualifier = getQualifier(attributes);
  144. if (StringUtils.hasText(qualifier)) {
  145. alias = qualifier;
  146. }
  147. // 5.定义BeanDefinitionHolder
  148. BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
  149. BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
  150. }
  151. private void validate(Map<String, Object> attributes) {
  152. AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);
  153. // This blows up if an aliased property is overspecified
  154. // FIXME annotation.getAliasedString("name", FeignClient.class, null);
  155. Assert.isTrue(
  156. !annotation.getClass("fallback").isInterface(),
  157. "Fallback class must implement the interface annotated by @FeignClient"
  158. );
  159. Assert.isTrue(
  160. !annotation.getClass("fallbackFactory").isInterface(),
  161. "Fallback factory must produce instances of fallback classes that implement the interface annotated by @FeignClient"
  162. );
  163. }
  164. private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
  165. Object configuration) {
  166. // 创建一个使用FeignClientSpecification构建的BeanDefinitionBuilder的类
  167. BeanDefinitionBuilder builder = BeanDefinitionBuilder
  168. .genericBeanDefinition(FeignClientSpecification.class);
  169. builder.addConstructorArgValue(name);
  170. builder.addConstructorArgValue(configuration);
  171. registry.registerBeanDefinition(
  172. name + "." + FeignClientSpecification.class.getSimpleName(),
  173. builder.getBeanDefinition());
  174. }
  175. ......
  176. ......
  177. ......
  178. }

在这里做了两件事情:

  1. 将EnableFeignClients注解对应的配置属性注入;
  2. 将FeignClient注解对应的属性注入。

生成FeignClient对应的bean,注入到Spring 的IOC容器。

总结

在我们查看处理@EnableFeignClients和@FeignClient注解的地方,最后调用registerFeignClient() 会构造一个:

  1. BeanDefinitionBuilder definition = BeanDefinitionBuilder
  2. .genericBeanDefinition(FeignClientFactoryBean.class);

所以我们后续会重点查看FeignClientFactoryBean 这个类。

申明

本文章首发自本人博客:https://www.cnblogs.com/wang-meng 和公众号:壹枝花算不算浪漫,如若转载请标明来源!

感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫

【一起学源码-微服务】Feign 源码一:源码初探,通过Demo Debug Feign源码的更多相关文章

  1. 【一起学源码-微服务】Eureka+Ribbon+Feign阶段性总结

    前言 想说的话 这里已经梳理完Eureka.Ribbon.Feign三大组件的基本原理了,今天做一个总结,里面会有一个比较详细的调用关系流程图. 说明 原创不易,如若转载 请标明来源! 博客地址:一枝 ...

  2. 【一起学源码-微服务】Ribbon源码五:Ribbon源码解读汇总篇~

    前言 想说的话 [一起学源码-微服务-Ribbon]专栏到这里就已经全部结束了,共更新四篇文章. Ribbon比较小巧,这里是直接 读的spring cloud 内嵌封装的版本,里面的各种config ...

  3. 【一起学源码-微服务】Nexflix Eureka 源码十:服务下线及实例摘除,一个client下线到底多久才会被其他实例感知?

    前言 前情回顾 上一讲我们讲了 client端向server端发送心跳检查,也是默认每30钟发送一次,server端接收后会更新注册表的一个时间戳属性,然后一次心跳(续约)也就完成了. 本讲目录 这一 ...

  4. 【一起学源码-微服务】Nexflix Eureka 源码十三:Eureka源码解读完结撒花篇~!

    前言 想说的话 [一起学源码-微服务-Netflix Eureka]专栏到这里就已经全部结束了. 实话实说,从最开始Eureka Server和Eureka Client初始化的流程还是一脸闷逼,到现 ...

  5. 【一起学源码-微服务】Feign 源码二:Feign动态代理构造过程

    前言 前情回顾 上一讲主要看了@EnableFeignClients中的registerBeanDefinitions()方法,这里面主要是 将EnableFeignClients注解对应的配置属性注 ...

  6. 【一起学源码-微服务】Nexflix Eureka 源码二:EurekaServer启动之配置文件加载以及面向接口的配置项读取

    前言 上篇文章已经介绍了 为何要读netflix eureka源码了,这里就不再概述,下面开始正式源码解读的内容. 如若转载 请标明来源:一枝花算不算浪漫 代码总览 还记得上文中,我们通过web.xm ...

  7. 【一起学源码-微服务】Hystrix 源码一:Hystrix基础原理与Demo搭建

    说明 原创不易,如若转载 请标明来源! 欢迎关注本人微信公众号:壹枝花算不算浪漫 更多内容也可查看本人博客:一枝花算不算浪漫 前言 前情回顾 上一个系列文章讲解了Feign的源码,主要是Feign动态 ...

  8. 【一起学源码-微服务】Netflix Eureka 源码一:Netflix Eureka 源码初探,我们为什么要读源码?

    前言 最近发现 网上好多自己的博客,很多朋友转载了文章却不加下 原载地址,本文欢迎转载一起学习,请在目录出加上原出处,感谢.转载来自:博客(一枝花算不算浪漫) 看了前面几篇文章的小伙伴知道,前几天在学 ...

  9. springcloud 项目源码 微服务 分布式 Activiti6 工作流 vue.js html 跨域 前后分离

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

随机推荐

  1. HTTP协议详解以及URL具体访问过程(转载)

    https://blog.csdn.net/f45056231p/article/details/82533490

  2. hdu 2986 Ballot evaluation (Simulation)

    Problem - 2986 之前在华工赛见过的一道简单的模拟,用map轻松干掉.为了精确,要全程用整型比较.轻松1y~ 代码如下: #include <cstdio> #include ...

  3. RegExp类型

    一.创建正则表达式的方法 1.字面量形式 var expressiion=/pattern/flags; flags:g全局模式,即将被应用于所有字符串,而非在发现第一个匹配项时立即停止: i不区分大 ...

  4. Python--day49--ORM框架SQLAlchemy之relationship的使用(有时间要从新看,这里状态不好,没有仔细听)

    小贴士:   迭代器:只有在循环的时候才一个一个往外拿 relationship

  5. java 集合类 & 容器

    为什么出现集合类? 面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就要对对象进行存储,集合就是存储对象最常用的一种方式. 数组和集合类同是容器,有何不同? 数组虽然也可以存储 ...

  6. mysql导入文件出现Data truncated for column 'xxx' at row 1的原因

    mysql导入文件的时候很容易出现"Data truncated for column 'xxx' at row x",其中字符串里的xxx和x是指具体的列和行数. 有时候,这是因 ...

  7. 【js】vue 2.5.1 源码学习 (三) Vue.extend 和 data的合并策略

    大体思路 (三)  1. 子类父类  2.Vue.extend()      //创建vue的子类 组件的语法器 Vue.extend(options) Profile().$mount('#app' ...

  8. 2018-11-5-win10-uwp-异步转同步

    title author date CreateTime categories win10 uwp 异步转同步 lindexi 2018-11-05 10:18:40 +0800 2018-2-13 ...

  9. 2018-11-1-Windows-Community-Toolkit-3.0-新功能

    title author date CreateTime categories Windows Community Toolkit 3.0 新功能 lindexi 2018-11-1 9:2:55 + ...

  10. Linux 内核控制 urb

    控制 urb 被初始化几乎和 块 urb 相同的方式, 使用对函数 usb_fill_control_urb 的 调用: void usb_fill_control_urb(struct urb *u ...