SpringCloud @FeignClient 注入Spring容器原理

前言

本文分析@FeignClient注解如何别扫描并注入到spring容器中,重点分析 @EnableFeignClients工作原理。由于通过源码分析涉及内容比较多建议根据文章中流程debug调试进行学习。

文章涉及 容器刷新模板方法,ConfigurationClassPostProcessor(bean工厂后置处理器),@Import注解等工作原理分析

@EnableFeignClients分析

在分析前先提出几个问题:

@EnableFeignClients通过什么原理可以把自己加到spring启动的生命周期中完成feign的bean扫描?

Sprintboot run方法如何能扫描 bean definition并放入spring容器中的?

Springboot启动阶段设置了哪些BeanFactoryPostProcessor到容器中?

本文在分析的过程中会将上述问题逐一讲解。在@EnableFeignClients注解中可以看到该注解主要功能:

  • 扫描声@FeignClient 注解声明的类
  • @FeignClient注解的类注入后可通过@Autowire @Component方式进行使用。类似@Configuration。

真正实现这些功能其实通过@Import注解+FeignClientsRegistrar类实现。

@Import 注解在spring启动生命周期中通过组合 ImportSelector实现类或者 ImportBeanDefinitionRegistrar实现类完成bean definition 加载

@EnableFeignClients就是用过这种机制完成@FeignClient的扫描

SpringCloud @FeignClient 注入Spring容器原理_第1张图片

在springboot中@Import 注解加载bean definition是通过Spring的后置处理器 BeanFactoryPostProcessor完成。

源码调用分析

下面结合Springboot整体启动的流程分析下@EnableFeignClients如何被加载的,主要分析关键逻辑具体细节不在此处展开。

  1. 首先SpringApplication run 方法启动
  2. 执行refresh方法 该方法为 AbstractApplicationContext 模板方法
  3. 执行 invokeBeanFactoryPostProcessors方法 该方法会将实现了BeanDefinitionRegistryPostProcessor类的后置处理进行实例化并调用
  4. 执行 ConfigurationClassPostProcessor 后置处理处理@ComponentScan @Import @ImportResources @PropertySource等注解
  5. 调用FeignClientsRegistrar类的解析bean definition方法

SpringCloud @FeignClient 注入Spring容器原理_第2张图片

接下来分析AbstractApplicationContext 的refresh方法中invokeBeanFactoryPostProcessors调用逻辑。此方法主要实例化 BeanFactoryPostProcessor并调用 postProcessBeanFactory方法。特别提示所有BeanFactoryPostProcessor实例化一定要在所有bean初始化前。

SpringCloud @FeignClient 注入Spring容器原理_第3张图片

 重点分析invokeBeanFactoryPostProcessors方法及bean后置处理器调用逻辑

PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

方法逻辑比较长但很好理解下图中红色框逻辑完全一样都是从当前bean定义中找到 BeanDefinitionRegistryPostProcessor实现类然筛选出优先级注解类 PriorityOrdered跟排序注解类Ordered并调用完成所有bean的扫描并注册到容器中扫描来源分为:注解&xml。

SpringCloud @FeignClient 注入Spring容器原理_第4张图片

 完成所有bean定义扫描类的后置处理器为 ConfigurationClassPostProcessor SpringCloud @FeignClient 注入Spring容器原理_第5张图片

ConfigurationClassPostProcessor 的 postProcessBeanDefinitionRegistry方法开始解析bean 定义。

SpringCloud @FeignClient 注入Spring容器原理_第6张图片

 postProcessBeanDefinitionRegistry中核心逻辑是通过配置类解析器进行解析,配置类一般为Springboot中@SpringbootApplication注解修饰类。

 SpringCloud @FeignClient 注入Spring容器原理_第7张图片

此处为Springboot启动时解析入口 ,通过配置类分析

SpringCloud @FeignClient 注入Spring容器原理_第8张图片

SpringCloud @FeignClient 注入Spring容器原理_第9张图片

 doProcessConfigurationClass方法开始解析各种常用注解如:@Component @Import等

protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate filter)
			throws IOException {

		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			// Recursively process any member (nested) classes first
			processMemberClasses(configClass, sourceClass, filter);
		}

		// Process any @PropertySource annotations
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
				processPropertySource(propertySource);
			}
			else {
				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}

		// Process any @ComponentScan annotations
		Set componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
			for (AnnotationAttributes componentScan : componentScans) {
				// The config class is annotated with @ComponentScan -> perform the scan immediately
				Set scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}

		// Process any @Import annotations
		processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

		// Process any @ImportResource annotations
		AnnotationAttributes importResource =
				AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
		if (importResource != null) {
			String[] resources = importResource.getStringArray("locations");
			Class readerClass = importResource.getClass("reader");
			for (String resource : resources) {
				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
				configClass.addImportedResource(resolvedResource, readerClass);
			}
		}

		// Process individual @Bean methods
		Set beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}

		// Process default methods on interfaces
		processInterfaces(configClass, sourceClass);

		// Process superclass, if any
		if (sourceClass.getMetadata().hasSuperClass()) {
			String superclass = sourceClass.getMetadata().getSuperClassName();
			if (superclass != null && !superclass.startsWith("java") &&
					!this.knownSuperclasses.containsKey(superclass)) {
				this.knownSuperclasses.put(superclass, configClass);
				// Superclass found, return its annotation metadata and recurse
				return sourceClass.getSuperClass();
			}
		}

		// No superclass -> processing is complete
		return null;
	}

本文分析@Import注解调用逻辑

 解析Import注解中value并返回所有类SpringCloud @FeignClient 注入Spring容器原理_第10张图片

 开始加载bean定义

SpringCloud @FeignClient 注入Spring容器原理_第11张图片

loadBeanDefinitionsForConfigurationClass 方法开始加载Import注解中配置类。

SpringCloud @FeignClient 注入Spring容器原理_第12张图片

 通过调用栈信息最终找到执行FeignClientRegistrar接口

SpringCloud @FeignClient 注入Spring容器原理_第13张图片

SpringBoot 注解加载流程逻辑

为了对Springboot中各个注解是在Spring生命周期每个阶段时如何执行的可以参考下图,具体流程可以单步debug进行分析

SpringCloud @FeignClient 注入Spring容器原理_第14张图片

 总结

本文简单分析了SpringBoot加载bean definition与FeignClient加载流程,由于细节逻辑太多本文不在展开分析。

你可能感兴趣的:(spring技术分享,spring,cloud,spring,java)