前言

打算系统分析下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. Go学习笔记3

    九.错误处理 1.defer+recover机制处理异常错误 展示错误: 发现:程序中出现错误/恐慌以后,程序被中断,无法继续执行. 错误处理/捕获机制: 内置函数recover: 2.自定义错误 需 ...

  2. redis基本数据类型 List

    127.0.0.1:6379> LPUSH test a (integer) 1 127.0.0.1:6379> LPUSH test b (integer) 2 127.0.0.1:63 ...

  3. 关于Teamcenter RAC开发如何查看Soa调用情况,已经查看反编译源码

  4. Linux系列教程——Linux文件编辑、Linux用户管理

    @ 目录 1 Linux基本权限 1.权限基本概述 1.什么是权限? 2.为什么要有权限? 3.权限与用户之间的关系? 4.权限中的rwx分别代表什么含义? 2.权限设置示例 1.为什么要设定权限,我 ...

  5. python系列:argparse详解 外部传参给python的库

    一.argparse简介 argparse 模块是 Python 内置的用于命令项选项与参数解析的模块,argparse 模块可以让人轻松编写用户友好的命令行接口,能够帮助程序员为模型定义参数. ar ...

  6. Iksevi 题解

    Iksevi 题目大意 \(n\) 次询问,每次给定一个点 \((x,y),x\ge 0, y\ge 0\),问有多少种对角线长为偶数的正方形使得在用该正方形正密铺第一象限的情况下该点位于正方形顶点上 ...

  7. 使用VSCode新建解决方案,添加ClassLib类库工程

    最近准备全面转向VSCode开发C#代码,所以第一件事就是使用VSCode新建解决方案,添加工程. 通过ChatGPT找到的大致的实现方案: 首先,打开VS Code的终端,然后导航到您想要创建解决方 ...

  8. fastjson反序列化Map对象为null?其实它只是一个地址,试试Map深拷贝

    今天遇到一个小问题,我用fastjson,把一个map对象转为实体类对象返回给前端的时候,map对象里面的一个属性既然为null. 由于前端需求,我们需要返回一个json里面,有两个属性是一样的(都是 ...

  9. 2D物理引擎 Box2D for javascript Games 第七章 子弹和感应器

    2D物理引擎 Box2D for javascript Games 第七章 子弹和感应器 你知道 Box2D 可以在每一个时间步中管理刚体间的碰撞并决算它们. 总之,在愤怒的小鸟中制作攻城机器期间,发 ...

  10. 在vue中如何使用echart

    1.在前面基础上搭建好vue环境,初始化webpack后,在终端使用npm i echarts -s 下载echart 2.下载完成后全局使用echart 在main.js文件中导入 import e ...