前言

打算系统分析下Feign的代码,上一篇讲了下Feign的历史,本篇的话,先讲下Feign相关的beanDefinition,beanDefinition就是bean的设计图,bean都是按照beanDefinition来制造的。

Feign相关的bean不少,有一些是因为我们的Feign相关注解而引入的,有一部分是因为spring的自动装配来自动引入的。

今天讲讲因为我们注解引入的那些。

EnableFeignClients引入的FeignClientSpecification

如果项目用到Feign,在@SpringBootApplication注解的主类上,我们一般还会加上@EnableFeignClients注解。

package a.b.c;

@SpringBootApplication
@EnableFeignClients
public class DemoApplication

实际上,spring的beanFactory初始化一般就是分两步,第一步,收集beanDefinition列表,第二步,根据beanDefinition生成并初始化bean。

收集beanDefinition相当重要,但是beanDefinition从哪来来呢,一开始的时候,都是框架默认注册了几个,应用自身的beanDefinition一般要从主类来,也就是上面说的@SpringBootApplication注解的主类。

一开始就会去解析主类上的注解,包名;解析包名的原因是,拿到包名后,默认就会去扫描这个包名下的class,再找到注解了@configuration、@controller、@service、@component之类的bean作为beanDefinition;解析注解的原因是,可以根据注解,引入更多的beanDefinition。

以@EnableFeignClients为例,该注解的定义如下:

@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients

所以这里会通过Import引入更多的beanDefinition。

org.springframework.cloud.openfeign.FeignClientsRegistrar

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 1
registerDefaultConfiguration(metadata, registry);
// 2
registerFeignClients(metadata, registry);
}

上面的1处,如下:

Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
String name = "default." + metadata.getClassName();
registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration")); private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(builder.getBeanDefinition());
}

主要是获取@EnableFeignClients这个注解相关的默认属性,然后注册了一个FeignClientSpecification类型的bean。

这个FeignClientSpecification类很简单,属性就两个:

public class FeignClientSpecification implements NamedContextFactory.Specification {

	private String name;

	private Class<?>[] configuration;
}

一个name,一个configuration,其实就是代表了一个要如何创建和配置FeignClient的配置,包含了如何创建feign的encoder、decoder等。

A custom @Configuration for all feign clients. Can contain override @Bean definition for the pieces that make up the client, for instance feign.codec.Decoder, feign.codec.Encoder, feign.Contract.

我们举个例子,如下的代码,

package a.b.c;

@SpringBootApplication
@EnableFeignClients
public class DemoApplication

最终生成的FeignClientSpecification beanDefinition,beanName为:default.a.b.c.DemoApplication.FeignClientSpecification,属性:

name:default.a.b.c.DemoApplication

configuration:空数组

FeignClient注解引入的FeignClientSpecification

说完这个,继续说如下的二处:

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 1
registerDefaultConfiguration(metadata, registry);
// 2
registerFeignClients(metadata, registry);
}

2处内部,是首先获取@EnableFeignClients注解的类所在的包名,然后在这个包名下扫描注解了@FeignClient的类,扫描到的这些类,那肯定是弄了一个beanDefinition了,这个可以倒推,毕竟每个@FeignClient注解的类最终都变成了一个bean嘛。

里面有一点值得讲,FeignClient注解中有一个属性,叫configuration,它支持你指定一个class,里面可以覆盖FeignClient内部的部分组件,如Feign的Encoder等

public @interface FeignClient {
String value() default "";
Class<?>[] configuration() default {};
}

这个你指定的配置类,是可以被多个FeignClient复用的,所以,spring内部也是只会存一份,这一份配置,就通过一个创建一个类型为FeignClientSpecification.class的bean来保存。

以如下为例:

@FeignClient(name = "demoService")
public interface DemoServiceFeignClient

由于没有指定configuration属性,这里生成的FeignClientSpecification bean中,name就是demoService,configuration就是null:

public class FeignClientSpecification implements NamedContextFactory.Specification {

	private String name;

	private Class<?>[] configuration;
}

如果指定configuration属性:

@FeignClient(name = "demoService", configuration = CustomConfig.class)
public interface DemoServiceFeignClient

FeignClientSpecification中的configuration就会是CustomConfig.class。

这个FeignClientSpecification.class的bean的名字,默认则会是demoService.FeignClientSpecification。

FeignClient对应的beanDefinition

接下来,就是注册一个FeignClient对应的beanDefinition了。

详细的源码可以看这里:org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClient

FeignClient的bean是较难创建的,所以这里是用了工厂bean:FeignClientFactoryBean

String name = getName(attributes);

FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
factoryBean.setRefreshableClient(isClientRefreshEnabled());
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
... return factoryBean.getObject();
}); BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);

这里的beanname,一般也就是FeignClient的全路径名。

汇总

最终其实就引入了三个bean:

首先是由@enableFeignClients引入的FeignClientSpecification,然后是@enableFeignClients注解所在的包名下的由@FeignClient注解引入的FeignClientSpecification,再一个就是FeignClient本身的bean(一个factoryBean)

Feign源码解析:初始化过程(一)的更多相关文章

  1. Feign源码解析

    1. Feign源码解析 1.1. 启动过程 1.1.1. 流程图 1.1.2. 解释说明 Feign解析过程依赖Spring的初始化,它通过实现ImportBeanDefinitionRegistr ...

  2. Feign源码解析系列-注册套路

    感谢不知名朋友的打赏,感谢你的支持! 开始 在追寻Feign源码的过程中发现了一些套路,既然是套路,就可以举一反三,所以值得关注. 这篇会详细解析Feign Client配置和初始化的方式,这些方式大 ...

  3. junit源码解析--初始化阶段

    OK,我们接着上篇整理.上篇博客中已经列出的junit的几个核心的类,这里我们开始整理junit完整的生命周期. JUnit 的完整生命周期分为 3 个阶段:初始化阶段.运行阶段和结果捕捉阶段. 这篇 ...

  4. springmvc源码解析-初始化

    1.      概述 对于Web开发者,MVC模型是大家再熟悉不过的了,SpringMVC中,满足条件的请求进入到负责请求分发的DispatcherServlet,DispatcherServlet根 ...

  5. mybatis源码阅读-初始化过程(七)

    说明 mybatis初始化过程 就是解析xml到封装成Configuration对象 供后续使用 SqlSessionFactoryBuilder 代码例子 SqlSessionFactoryBuil ...

  6. Feign源码解析系列-那些注解们

    开始 Feign在Spring Cloud体系中被整合进来作为web service客户端,使用HTTP请求远程服务时能就像调用本地方法,可见在未来一段时间内,大多数Spring Cloud架构的微服 ...

  7. 分布式事务_02_2PC框架raincat源码解析-启动过程

    一.前言 上一节已经将raincat demo工程运行起来了,这一节来分析下raincat启动过程的源码 主要包括: 事务协调者启动过程 事务参与者启动过程 二.协调者启动过程 主要就是在启动类中通过 ...

  8. Feign源码解析系列-最佳实践

    前几篇准备写完feign的源码,这篇直接给出Feign的最佳实践,考虑到目前网上还没有一个比较好的实践解释,对于新使用spring cloud的同学会对微服务之间的依赖产生一些迷惑,也会走一些弯路.这 ...

  9. Feign源码解析系列-核心初始化

    开始 初始化Feign客户端当然是整个过程中的核心部分,毕竟初始化完毕就等着调用了,初始化时候准备的什么,流程就走什么. 内容 从上一篇中,我们已经知道,对于扫描到的每一个有@FeignClient, ...

  10. Syncthing源码解析 - 启动过程

    我相信很多朋友会认为启动就是双击一下Syncthing程序图标,随后就启动完毕了!如果这样认为,对,也不对!对,是因为的确是这样操作,启动了Syncthing:不对是因为在调试Syncthing启动过 ...

随机推荐

  1. 【译】在 Visual Studio 中处理图像变得更容易了

    任何 Web.桌面或移动开发人员都经常使用图像.你可以从 C#.HTML.XAML.CSS.C++.TypeScript 甚至代码注释中引用它们.有些图像是本地的,有些存在于线上或网络共享中,而其他图 ...

  2. 设备维修保养通知:如何使用API接口发送通知给相关人员

    在设备维修保养管理中,及时通知相关人员是确保设备得到及时维护的关键.API接口提供了一个方便的方式来自动发送维修保养通知,以确保工作流程的顺利进行.本文将详细介绍如何使用成熟的API接口来发送设备维修 ...

  3. 「openjudge / poj - 1057」Chessboard

    link. 调起来真的呕吐,网上又没篇题解.大概是个不错的题. 首先行和列一定是独立的,所以我们把行列分开考虑.这样的问题就弱化为:在一个长度为 \(n\) 的格子带上,有 \(n\) 个物品,每个物 ...

  4. 「note」原根照抄

    阶(multiplicative order) \(\textbf{Def.}\):\(\delta_m(a)\) 为最小的 \(n\) 使得 \(a^n\equiv 1\pmod m\),其中 \( ...

  5. jdk8环境变量

    JAVA_HOME C:\Program Files\Java\jdk1.8.0_333 Path%JAVA_HOME%\bin%JAVA_HOME%\jre\bin CLASSPATH.%JAVA_ ...

  6. netstat命令输出详解

    netstat命令输出详解 1. 列出所有的TCP和UDP端口 2. 命令输出详解 Proto:协议名(tcp协议还是udp协议) recv-Q:网络接收队列,send-Q:网路发送队列 a. rec ...

  7. strimzi实战之一:简介和准备

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 关于strimzi strimzi是一个开源项目,已加 ...

  8. 贝塞尔曲线的切线及其AABB问题

    贝塞尔曲线的切线及其AABB问题 先聊点别的 2023 年抖音上居然还看到很多前端培训 各种直播前端教学(虽然是录播)但看起来还是有大批前往前端卷啊 说明了什么,很可能说明其它行业更难卷 这不是行业不 ...

  9. Go 常用命令介绍

    Go 常用命令 目录 Go 常用命令 一.Go 常用命令 1.1 go build 1.1.1 指定输出目录 1.1.2 常用环境变量设置编译操作系统和 CPU 架构 1.1.3 查看支持的操作系统和 ...

  10. 43道Python经典案例题(有答案)

    1.有四个数字:1.2.3.4,能组成多少个互不相同且无重复数字的三位数?各是多少? for x in range(0,5): for y in range(0,5): for z in range( ...