【微服务】SpringBoot启动流程注册FeignClient

目录

一、前言

二、注册FeignClient

1、 registerBeanDefinitions()逻辑

2、注册默认配置

2.1、registerDefaultConfiguration()逻辑

2.2、registerClientConfiguration()逻辑

3、注册FeignClient(一般是包下)

3.1、getBasePackages(metadata)逻辑

3.2、扫描包下的FeignClient

3.3、遍历扫描出来的候选组件,开始调用注册逻辑


一、前言

    现在许许多多的大大小小公司都使用了微服务框架,对程序员的要求不再是仅了解,面试的时候更是直面底层原理。要想深得面试官的青睐,还是得准备准备读读源码,亲自动动手探究一番方可临危不乱。读源码对面试有帮助,对理解框架设计也可提升你的认知,对日后碰到框架扩展的需求可以积累经验以及节约时间等。

二、注册FeignClient

【微服务】SpringBoot启动流程注册FeignClient_第1张图片

从上一节可见我们来到了spring-cloud-openfeign的FeignClientsRegistrar组件的 registerBeanDefinitions()处理逻辑。下面我们沿着它继续跟进

1、 registerBeanDefinitions()逻辑

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		registerDefaultConfiguration(metadata, registry);
		registerFeignClients(metadata, registry);
	}

【微服务】SpringBoot启动流程注册FeignClient_第2张图片

这里主要将相应的注册逻辑封装了出去,根据上一节解析的注解元数据(StandardAnotationMetadata)以及注册机(DefaultListableBeanFactory)完成默认配置的注册、对应模块下FeignClients的注册。

2、注册默认配置

2.1、registerDefaultConfiguration()逻辑

	private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		// 获取@EnableFeignClients全部属性
		Map defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

		if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
			String name;
			if (metadata.hasEnclosingClass()) {
				name = "default." + metadata.getEnclosingClassName();
			}
			else {
				// 如:default.com.ceam.AdminApp
				name = "default." + metadata.getClassName();
			}
			// 注册
			registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
		}
	}

【微服务】SpringBoot启动流程注册FeignClient_第3张图片

 拼接name,然后再调用registerClientConfiguration()完成注册。

2.2、registerClientConfiguration()逻辑

	private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
		// 构建Bean定义建造者
		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
		builder.addConstructorArgValue(name);
		builder.addConstructorArgValue(configuration);
		// 注册到DefaultListableBeanFactory
		registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
				builder.getBeanDefinition());
	}

【微服务】SpringBoot启动流程注册FeignClient_第4张图片

主要构建Bean定义,然后注册到DefaultListableBeanFactory(Bean定义注册中心)。

3、注册FeignClient(一般是包下)

	public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

		LinkedHashSet candidateComponents = new LinkedHashSet<>();
		// 获取@EnableFeignClients的全部属性
		Map attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
		// 获取@EnableFeignClients的clients属性,如果不是空的,则禁用类路径扫描。
		final Class[] clients = attrs == null ? null : (Class[]) attrs.get("clients");
		// 使用路径扫描
		if (clients == null || clients.length == 0) {
			// 获取扫描器,Spring在处理@RestController、@Service、@Component等时也用到该扫描器
			ClassPathScanningCandidateComponentProvider scanner = getScanner();
			// 设置资源加载器
			scanner.setResourceLoader(this.resourceLoader);
			// 设置Filter,只扫描@FeignClient注解
			scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
			// 从@EnableFeignClients中获取扫描包路径
			Set basePackages = getBasePackages(metadata);
			for (String basePackage : basePackages) {
				// 根据指定包路径扫描@FeignClient标注的接口,添加到candidateComponents集合
				candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
			}
		}
		else {
			// 不用类路径扫描
			for (Class clazz : clients) {
				candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
			}
		}

		// 遍历candidateComponents
		for (BeanDefinition candidateComponent : candidateComponents) {
			if (candidateComponent instanceof AnnotatedBeanDefinition) {
				// verify annotated class is an interface验证带注释的类是一个接口
				AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
				AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
				// @FeignClient 只能在接口上指定
				Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");

				// 获取@FeignClient的属性
				Map attributes = annotationMetadata
						.getAnnotationAttributes(FeignClient.class.getCanonicalName());

				// 获取@FeignClient中指定的服务名
				String name = getClientName(attributes);
				registerClientConfiguration(registry, name, attributes.get("configuration"));
                // 注册FeignClient
				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}

主要逻辑:

  • 获取@EnableFeignClients注解的属性,重点拿到扫描包路径(basePackages)。
  • 然后通过getScanner()方法获取扫描器:ClassPathScanningCandidateComponentProvider。
  • 接着给扫描器ClassPathScanningCandidateComponentProvider添加一个注解过滤器(AnnotationTypeFilter),只过滤出包含@FeignClient注解的BeanDefinition。
  • 再通过getBasePackages(metadata)方法获取@EnableFeingClients注解中的指定的包扫描路径 或 扫描类;如果没有获取到,则默认扫描启动类所在的包路径。
  • 然后进入到扫描逻辑:通过scanner.findCandidateComponents(basePackage)方法从包路径下扫描出所有标注了@FeignClient注解并符合条件装配的接口;组件内部通过isCandidateComponent(metadataReader)做过滤处理,重点是根据设置的过滤器过滤。
  • 最后将FeignClientConfiguration (主要是服务名称)在BeanDefinitionRegistry中注册一下,再对FeignClient做真正的注册操作。最终注册到DefaultListableBeanFactory

3.1、getBasePackages(metadata)逻辑

	protected Set getBasePackages(AnnotationMetadata importingClassMetadata) {
		// 获取@EnableFeignClients的属性
		Map attributes = importingClassMetadata
				.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());

		Set basePackages = new HashSet<>();
		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);
			}
		}
		// 指定类名情况下,通过ClassUtils获取指定类所在的包
		for (Class clazz : (Class[]) attributes.get("basePackageClasses")) {
			basePackages.add(ClassUtils.getPackageName(clazz));
		}

		// 如果上面两种情况下都没有获取到包路径,则按照启动类所在的包作为扫描路径
		if (basePackages.isEmpty()) {
			basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
		}
		return basePackages;
	}

【微服务】SpringBoot启动流程注册FeignClient_第5张图片

 没有指定包路径或扫描类,则通过ClassUtils获取启动类所在路径。

3.2、扫描包下的FeignClient

【微服务】SpringBoot启动流程注册FeignClient_第6张图片

 将包路径传递进来,开始扫描指定包下资源。

1)findCandidateComponents()逻辑

	public Set findCandidateComponents(String basePackage) {
		if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
			return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
		}
		else {
			// 999520一般走这里
			return scanCandidateComponents(basePackage);
		}
	}

扫描候选组件的类路径。

2)scanCandidateComponents(basePackage)

【微服务】SpringBoot启动流程注册FeignClient_第7张图片

【微服务】SpringBoot启动流程注册FeignClient_第8张图片 获取解析器来加载资源。

【微服务】SpringBoot启动流程注册FeignClient_第9张图片 获取路径匹配器处理

【微服务】SpringBoot启动流程注册FeignClient_第10张图片 查找路径匹配文件资源并添加到result

【微服务】SpringBoot启动流程注册FeignClient_第11张图片 可见获取的资源都是.class文件。

3)过滤出@FeignClient的.class资源

【微服务】SpringBoot启动流程注册FeignClient_第12张图片

【微服务】SpringBoot启动流程注册FeignClient_第13张图片

【微服务】SpringBoot启动流程注册FeignClient_第14张图片

若匹配返回true

4)添加到candidates集合

【微服务】SpringBoot启动流程注册FeignClient_第15张图片

【微服务】SpringBoot启动流程注册FeignClient_第16张图片

3.3、遍历扫描出来的候选组件,开始调用注册逻辑

【微服务】SpringBoot启动流程注册FeignClient_第17张图片

封装Bean定义,将封装好的Bean定义以及注册机传进来,调用BeanDefinitionReaderUtils注册。

【微服务】SpringBoot启动流程注册FeignClient_第18张图片

 BeanDefinitionReaderUtils工具类主要是交给注册机(DefaultListableBeanFactory)完成注册,它还可以对BeanName注册别名如果指定的话。【微服务】SpringBoot启动流程注册FeignClient_第19张图片

 最终还是注册到Bean定义注册中心DefaultListableBeanFactorybeanDefinitionMap字段。

你可能感兴趣的:(Spring家族,spring,spring,boot,java)