彻底搞懂Feign——EnableFeignClient底层机制探究

EnableFeignClient源码探究

话不多说直接开干!
点进@EnableFeignClient这个注解里:
在这里插入图片描述
里面较为核心的是这个FeignClientRegistrar,我们点进去:
在这里插入图片描述

我们发现它实现了ImportBeanDefinitionRegistrar、ResourceLoaderAware、EnvironmentAware这三个接口,这里关于后面连个Aware接口就不做过多介绍了,熟悉Spring的同学应该都知道吧,我们就看一下这个ImportBeanDefinitionRegistrar
在这里插入图片描述
这个是干嘛用的呢?我们二话不说,去ImportBeanDefinitionRegistrar上断点,学习一个框架最快的途径就是Debug,从头到尾Debug一番你就什么都了解了。
在这里插入图片描述
打上断点Debug启动后,可以看到它已经走到了这一行,第一个是registerDefaultConfiguration顾名思义,是注册默认配置,我们跟进去看看有什么名堂:
在这里插入图片描述

可以看到,它这里通过getAnnotationAttributes获取到了@EnableFeignClients注解里面所有的属性,只不过没有赋值。
紧接着:

	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"));
		}

因为我们没有继承其他类,所以我们这里获取的name是:在这里插入图片描述
最后又调用了registerClientConfiguration这个方法,我们点进去看看这个方法做了什么事情:
在这里插入图片描述
我们可以看到这个方法里,它传进来了一个BeanDefinitionRegistry,通过这个registry就构造出来了上下文中用到的Bean信息。
这个Builder先构造了FeignClientSpecification,然后把main函数的类名传进去,并且把配置项也传进去,然后就从这个BeanDefinitionRegistry里把Bean注册进去了。

然后我们再返回到registerBeanDefinitions这个方法里:
在这里插入图片描述
我们刚刚看完了第一个方法,是注册默认的配置,接下来这个registerFeignClients方法就和Feign息息相关了。
这个FeignClients就是这些东西:
在这里插入图片描述
我们来看看它是怎么注册的:
在这里插入图片描述
首先第一步,它是获取了一个scanner,这个scanner就是扫包用的,下面有设置了一个resourceLoader,平淡无奇,继续往下看:
在这里插入图片描述
在这里又一次获取了EnableFeignClients,并且下面声明了一个Filter,这个Filter它会过滤这个FeignClient注解的一些类。
接下来:
在这里插入图片描述
这里判断欲奴attrs是否为null,如果是不是null,就获取到声明的clients,因为我们在注解里没有指定要加载哪些clients,所以这个attrs就是空,紧接着:
在这里插入图片描述
这里如果没有指定加载哪些clients,就走到了这一步,这里用到了刚刚的Filter,就是加载所有用FeignClient注解声明的类,下面又从matedata里面获取了一个basePackages,我们跟进去看看是怎么获取的:

protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
    
    
		//从EnableFeignClients里获取属性
		Map<String, Object> attributes = importingClassMetadata
				.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());
		
		Set<String> basePackages = new HashSet<>();
		//下面这几个for循环都是获取属性值
		for (String pkg : (String[]) attributes.get("value")) {
    
    
			if (StringUtils.hasText(pkg)) {
    
    
				basePackages.add(pkg);
			}
		}
		for (String pkg : (String[]) attributes.get("basePackages")) {
    
    
			if (StringUtils.hasText(pkg)) {
    
    
				basePackages.add(pkg);
			}
		}
		for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
    
    
			basePackages.add(ClassUtils.getPackageName(clazz));
		}

		//如果都没有声明
		if (basePackages.isEmpty()) {
    
    
			basePackages.add(
					//拿当前声明的main函数的那个类来扫描
					ClassUtils.getPackageName(importingClassMetadata.getClassName()));
		}
		return basePackages;
	}

在这里插入图片描述
由于我们这里都没有声明,所以它走了备选方案,获取了EnableFeignClient声明的那个main函数的包。
我们获取到了这个basePackage后,来到了这里:
在这里插入图片描述
这里它就会循环这个basePackages,然后利用scanner对basePackage进行扫包,之前我们看到这个scanner加了一个AnnotationTypeFilter,那它就会把这个包路径下的所有加载了对应Annotation的Class或者Interface给找出来,我们往下走一步看看:
在这里插入图片描述
他给我们找出来了这个IService,然后它循环这个candidate:
在这里插入图片描述
首先,因为我们的IService是用FeignClient这个注解声明的,所以它肯定是AnnotatedBeanDefinition这种类型,所以进入到了if里面:

if (candidateComponent instanceof AnnotatedBeanDefinition) {
    
    
					// verify annotated class is an interface
					//转换成AnnotatedBeanDefinition
					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
					//获取Metadata,也就是获取主数据
					AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
					//这里要求annotationMetadata必须是一个接口,否则就报错
					Assert.isTrue(annotationMetadata.isInterface(),
							"@FeignClient can only be specified on an interface");

					//然后从annotationMetadata拿到FeignClient属性
					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());
					//获取clientName
					String name = getClientName(attributes);
					//注册配置信息
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));
					//注册FeignClient
					registerFeignClient(registry, annotationMetadata, attributes);
				}

我们来看这一步,它是怎么获取ClientName的:
在这里插入图片描述
跟进去:
在这里插入图片描述
首先,它看你注解里有没有声明contextId,如果没有,就会获取value:
在这里插入图片描述
这个value就是我们注解里声明的eureka-client。
如果value没拿到就从name里拿,name里没有就从serviceId拿,如果什么都拿不到,那么就会报错:
在这里插入图片描述
也就是说,当我们声明了FeignClient这个注解后,你必须给他指定一个serviceId,或者name、value等。
总之你要让它知道这个接口它要代理的对象,是需要把请求转发到哪一个微服务当中的,没有这个属性Feign可是没法工作的。

我们再看下一个方法:
在这里插入图片描述
跟进去:
在这里插入图片描述
这一步是注册Client的配置信息,实际上这一波就是在上下文中把BeanDefinitioin构造出来。
非常简单,就是传入name、配置项,最终再registry里面把这个Bean给注册进去。

我们返回:
在这里插入图片描述
最后这一步就是注册FeignClient了,跟进去看看:
在这里插入图片描述
前面简单,首先获取了Feign声明的类的全限定类名,然后声明了一个BeanDefinitionBuilder,然后这里对属性进行了一番验证,我们跟进去看一下:
在这里插入图片描述
这里是涉及Hystrix降级容错的一些逻辑,我们暂且先不管。
再返回:
在这里插入图片描述
这里进行完一大堆属性的设置后,这里设置了一个注入类型,是根据类型进行注入。

我们再往下走,看到这里:
在这里插入图片描述
这里构造了一个BeanDefinitionHolder,并且齐了一个别名,然后通过registerBeanDefinition和BeanDefinitionRegistry关联了起来,完成了注册。

到这里我们整个registerBeanDefinitions的过程已经结束了。

猜你喜欢

转载自blog.csdn.net/qq_45455361/article/details/121459795