Feign踩坑源码分析--@FeignClient注入容器
一. @EnableFeignClients
1.1.类介绍
从上面注释可以看出是扫描声明了@FeignClient接口的类,还引入了 FeignClientsRegistrar类,从字面意思可以看出是进行了 FeignClient 客户端类的注册。
1.2.FeignClientsRegistrar 详解
最主要的一个方法:registerBeanDefinitions注册bean定义信息,主要功能是实现向容器注册Feign客户端配置信息和所有的使用了@FeignClient的类;

1.2.1.registerDefaultConfiguration()
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 获取@EnableFeignClients注解的属性和值
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
// 获取属性中的默认配置 defaultConfiguration,name是main主程序
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
//默认配置信息进行容器注册
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}

1.2.1.1.registerClientConfiguration
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
// 创建一个 FeignClientSpecification.class 构造器
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
// 向容器中注册默认配置
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
1.2.1.2.registerBeanDefinition
核心代码在:org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException { ............
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
//放到beanDefinitionMap,到AbstractApplicationContext#finishBeanFactoryInitialization(beanFactory)中取出进行bean实例化
//此时是处于invokeBeanFactoryPostProcessors(beanFactory);阶段,进行BeanDefinitionRegistryPostProcessor的处理
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
removeManualSingletonName(beanName);
}
}
..........
}
1.2.2.registerFeignClients()

public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//获取一个扫描器
ClassPathScanningCandidateComponentProvider scanner = getScanner();
//设置资源路径
scanner.setResourceLoader(this.resourceLoader);
//包路径
Set<String> basePackages;
//获取EnableFeignClients注解的参数和值,有5个值,其中clients对应的class[0]是没有值的
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
//定义一个 FeignClient 注解类型过滤器
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
//clients为class[0],长度为0
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
//FeignClient 注解过滤器添加到扫描器中
scanner.addIncludeFilter(annotationTypeFilter);
//获取Application对应的根路径包
basePackages = getBasePackages(metadata);
}
//clients不为空:FeignClient 注解过滤器添加到扫描器中、获取包路径
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)));
}
//遍历包路径,获取标记@FeignClient注解的接口向容器中注入
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");
//获取@FeignClient的参数和值
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName()); String name = getClientName(attributes);
//再次更新配置
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//注册
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}

1.2.2.2.registerFeignClient
//定义一个 FeignClientFactoryBean bean定义构造器(重点)
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
..........
//前面根据attributes进行属性赋值后注入到Spring容器中
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
二.@FeignClient
从上面可以看到在注册客户端时注册了一个FeignClientFactoryBean(对于FactoryBean的扩展知识和案例),所以FeignClient的获取是从getObject()获取的:

2.1.getTarget()
<T> T getTarget() {
//获取创建feign实例的工厂
FeignContext context = this.applicationContext.getBean(FeignContext.class);
//获取feign构造器
Feign.Builder builder = feign(context);
//url拼接
if (!StringUtils.hasText(this.url)) {
//拼接name
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
//拼接path
this.url += cleanPath();
//创建实例
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}
2.1.1. feign()
//添加日志对象、编码器、解码器、解析规则器
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type); // @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
//SpringMvcContract:对RequestMapping、RequestParam、RequestHeader等注解进行解析
.contract(get(context, Contract.class));
// @formatter:on configureFeign(context, builder); return builder;
}
2.1.2. loadBalance()
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
//根据context上下文获取客户端实例,client:TraceLoadBalancerFeignClient,负载均衡
Client client = getOptional(context, Client.class);
if (client != null) {
//客户端创建
builder.client(client);
//获取HystrixTargerer
Targeter targeter = get(context, Targeter.class);
//调用HystrixTargeter#target进行实例创建
return targeter.target(this, builder, context, target);
} throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}



2.1.3. HystrixTargeter#target
class HystrixTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
// 不是同一种类型,进入feign#target方法
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(target);
}
......
}

2.1.4. Feign.Builder#target
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
2.1.4.1. Feign.Builder#target
public Feign build() {
//同步方法处理工厂,构建了日志级别信息
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy);
//解析信息:编码、解密、同步方法处理工厂
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
//new对象
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
2.1.4.2.ReflectiveFeign#newInstance
@Override
public <T> T newInstance(Target<T> target) {
//得到feign类的定义方法
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
//定义方法存放集合
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
//默认方法存放集合
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
//对feign类方法进行遍历
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
//默认方法
} else if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
//jdk动态代理创建对象
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;
}


三.总结
主要应用了FactoryBean的特性getObject()进行jdk动态创建一个ReflectiveFeign的代理对象。
Feign踩坑源码分析--@FeignClient注入容器的更多相关文章
- Spring源码分析专题 —— IOC容器启动过程(上篇)
声明 1.建议先阅读<Spring源码分析专题 -- 阅读指引> 2.强烈建议阅读过程中要参照调用过程图,每篇都有其对应的调用过程图 3.写文不易,转载请标明出处 前言 关于 IOC 容器 ...
- 【spring源码分析】IOC容器初始化(三)
前言:在[spring源码分析]IOC容器初始化(二)中已经得到了XML配置文件的Document实例,下面分析bean的注册过程. XmlBeanDefinitionReader#registerB ...
- 【spring源码分析】IOC容器初始化(四)
前言:在[spring源码分析]IOC容器初始化(三)中已经分析了BeanDefinition注册之前的一些准备工作,下面将进入BeanDefinition注册的核心流程. //DefaultBean ...
- 【spring源码分析】IOC容器初始化(七)
前言:在[spring源码分析]IOC容器初始化(六)中分析了从单例缓存中加载bean对象,由于篇幅原因其核心函数 FactoryBeanRegistrySupport#getObjectFromFa ...
- 【spring源码分析】IOC容器初始化(十)
前言:前文[spring源码分析]IOC容器初始化(九)中分析了AbstractAutowireCapableBeanFactory#createBeanInstance方法中通过工厂方法创建bean ...
- 【spring源码分析】IOC容器初始化(总结)
前言:在经过前面十二篇文章的分析,对bean的加载流程大致梳理清楚了.因为内容过多,因此需要进行一个小总结. 经过前面十二篇文章的漫长分析,终于将xml配置文件中的bean,转换成我们实际所需要的真正 ...
- 【spring源码分析】IOC容器初始化(二)
前言:在[spring源码分析]IOC容器初始化(一)文末中已经提出loadBeanDefinitions(DefaultListableBeanFactory)的重要性,本文将以此为切入点继续分析. ...
- 【spring源码分析】IOC容器初始化——查漏补缺(一)
前言:在[spring源码分析]IOC容器初始化(十一)中提到了初始化bean的三个步骤: 激活Aware方法. 后置处理器应用(before/after). 激活自定义的init方法. 这里我们就来 ...
- 【spring源码分析】IOC容器初始化——查漏补缺(五)
前言:我们知道在Spring中经常使用配置文件的形式对进行属性的赋值,那配置文件的值是怎么赋值到属性上的呢,本文将对其进行分析. 首先了解一个类:PropertySourcesPlaceholderC ...
- 【spring源码分析】IOC容器初始化——查漏补缺(二)
前言:在[spring源码分析]IOC容器初始化(八)中多次提到了前置处理与后置处理,本篇文章针对此问题进行分析.Spring对前置处理或后置处理主要通过BeanPostProcessor进行实现. ...
随机推荐
- MySQL count(*) 和 count(字段) 区别
count(字段)不会取Null的 select count(*) from test01 7条 select count(0) from test01 ...
- swagger 增强UI --knife4j
knife4j 文档: https://gitee.com/xiaoym/knife4j (1)简介 knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是sw ...
- 《MySQL是怎样运行的》第六章小结
- What is UDS Service 0x10 - Diagnostic Session Control ?
Why need the UDS Service 0x10? ECU在正常工作时会处于某一个会话模式下,上电后会自动进入默认会话模式,所以ECU启动后我们不需要输入0x10 01来进入该会话模式.EC ...
- Cryptanalyzing and Improving a Novel Color Image Encryption Algorithm Using RT-Enhanced Chaotic Tent Maps
Cryptanalyzing and Improving a Novel ColorImage Encryption Algorithm Using RT-EnhancedChaotic Tent M ...
- operator简介
原理 operator 是一种 kubernetes 的扩展形式,利用自定义资源对象(Custom Resource)来管理应用和组件,允许用户以 Kubernetes 的声明式 API 风格来管理应 ...
- return、break与continue的区别
前言 在上一篇文章中,壹哥给大家介绍了while.do-while两种循环结构,并且给大家总结了两种循环的区别.实际上,我们在利用循环执行重复操作的过程中,还存在着另一个需求:如何中止,或者说提前结束 ...
- 快速傅里叶变换应用(FFT Applications)
1. 3-SUM 1.1 问题描述 Given three sets \(X\), \(Y\), and $Z $ of \(n\) integers each, determine whether ...
- MQTT(EMQX) - Java 调用 MQTT Demo 代码
POM <dependency> <groupId>org.eclipse.paho</groupId> <artifactId>org.eclipse ...
- python实现桌面截图功能
PIL中的ImageGrab模块 import time import numpy as np from PIL import ImageGrab img = ImageGrab.grab(bbox= ...
