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的扫描

在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加载流程,由于细节逻辑太多本文不在展开分析。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Cloud是一个用于构建分布式系统的开发工具集合,它提供了许多有用的组件,包括Zuul、Ribbon、Hystrix和@FeignClient。 Zuul是Spring Cloud中的网关服务,它可以将各个微服务的请求路由到相应的微服务上。Zuul具有负载均衡和过滤器等功能,可以对请求进行拦截和处理。 Ribbon是一个负载均衡器,它可以根据负载情况将请求分发给不同的微服务实例。Ribbon可以与Eureka等注册中心配合使用,动态地获取可用的服务实例列表,并根据一定的负载均衡策略选择合适的实例。 Hystrix是一个容错和延迟容忍的库,可以帮助我们构建稳定的分布式系统。它可以防止由于某一微服务的故障或延迟而导致整个系统的崩溃。通过为每个外部服务的调用添加断路器,Hystrix可以在外部服务不可用时提供备选方案,并且可以对外部服务的调用进行监控和度量。 @FeignClient是一个用于声明式REST客户端的注解。通过在接口中添加@FeignClient注解,并指定要访问的微服务名称,我们可以方便地进行REST调用。Feign会根据接口定义自动生成实现类,并将请求发送到相应的微服务。 综上所述,Spring Cloud中的Zuul、Ribbon、Hystrix和@FeignClient是用于构建分布式系统的重要组件。它们可以帮助我们解决微服务架构中的路由、负载均衡、容错和服务间调用等问题。利用这些组件,我们可以更方便地构建可靠、高效的分布式系统。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值