SpringCloud @FeignClient注入Spring容器原理分析

 更新时间:2024年12月31日 10:59:09   作者:systemup_v1  
本文详细分析了Spring Boot中@FeignClient注解的扫描和注入过程,重点探讨了@EnableFeignClients注解的工作原理,通过源码分析,揭示了@EnableFeignClients如何通过@Import注解和FeignClientsRegistrar类实现bean定义的加载

前言

本文分析@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的扫描

在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方法

接下来分析AbstractApplicationContext 的refresh方法中invokeBeanFactoryPostProcessors调用逻辑。

此方法主要实例化 BeanFactoryPostProcessor并调用 postProcessBeanFactory方法。

特别提示所有BeanFactoryPostProcessor实例化一定要在所有bean初始化前。

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

PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

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

完成所有bean定义扫描类的后置处理器为 ConfigurationClassPostProcessor

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

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

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

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

protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> 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<AnnotationAttributes> 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<BeanDefinitionHolder> 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<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
			for (String resource : resources) {
				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
				configClass.addImportedResource(resolvedResource, readerClass);
			}
		}

		// Process individual @Bean methods
		Set<MethodMetadata> 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并返回所有类

开始加载bean定义

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

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

SpringBoot 注解加载流程逻辑

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

总结

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

相关文章

  • @CacheEvict中的allEntries与beforeInvocation的区别说明

    @CacheEvict中的allEntries与beforeInvocation的区别说明

    这篇文章主要介绍了@CacheEvict中的allEntries与beforeInvocation的区别说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • window系统安装jdk jre的教程图解

    window系统安装jdk jre的教程图解

    java开发少不了安装jdk,jdk可以同时安装多个版本,只要在项目部署时注意切换版本选择,下面小编给大家带来了window系统安装jdk jre的教程图解,感兴趣的朋友一起看看吧
    2018-08-08
  • JAVA 实现磁盘文件加解密操作的示例代码

    JAVA 实现磁盘文件加解密操作的示例代码

    这篇文章主要介绍了JAVA 实现磁盘文件加解密操作的示例代码,帮助大家利用Java实现文件的加解密,感兴趣的朋友可以了解下
    2020-09-09
  • java实现简易飞机大战

    java实现简易飞机大战

    这篇文章主要为大家详细介绍了java实现简易飞机大战,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • 搭建Java开发环境JDK、IDE的详细图文教程

    搭建Java开发环境JDK、IDE的详细图文教程

    本文详细介绍了如何搭建Java开发环境,包括下载JDK、配置环境变量、安装IDE以及编写和运行第一个Java程序,本文通过图文并茂的形式给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2025-11-11
  • Java利用poi实现word表格转excel

    Java利用poi实现word表格转excel

    这篇文章主要为大家详细介绍了Java如何利用poi实现word表格转excel,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2025-03-03
  • Java maven详细介绍

    Java maven详细介绍

    今天给大家复习一下Java基础知识,简单介绍Maven,文中有非常详细的解释,对Java初学者很有帮助哟,需要的朋友可以参考下,希望能够给你带来帮助
    2021-09-09
  • SpringBoot项目速度提升之延迟初始化(Lazy Initialization)详解

    SpringBoot项目速度提升之延迟初始化(Lazy Initialization)详解

    延迟初始化(Lazy Initialization)是一种在需要时才创建或加载对象的策略,以减少启动时间和资源消耗,本文就来讲讲延迟初始化的具体使用吧
    2023-05-05
  • HttpClient 请求 URL字符集转码问题

    HttpClient 请求 URL字符集转码问题

    这篇文章主要介绍了HttpClient 请求 URL字符集转码问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-01-01
  • Java实现根据地址智能识别省市区县

    Java实现根据地址智能识别省市区县

    这篇文章主要为大家详细介绍了如何编写一个Java工具类,可以根据身份证地址或用户输入的地址,智能识别并提取出详细的省市区县信息,感兴趣的小伙伴可以了解下
    2025-03-03

最新评论