• SpringCloud原理-OpenFeign篇(二、OpenFeign包扫描和FeignClient的注册原理)


    前言

    本篇是SpringCloud原理系列的 OpenFeign 模块的第二篇。主要研究是使用了FeignClient 注解的接口的初始化原理。也就是它是如何将什么类型的实例,放到容器中的。

    另外,本文附录中,图解了本文代码的执行链路。

    使用java 17,spring cloud 4.0.4,springboot 3.1.4

    正文

    一、从启动类开始

    先看看如下启动类:

    package org.feng;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @EnableFeignClients(basePackages = "org.feng.feigns")
    @SpringBootApplication
    public class ClientApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ClientApplication.class, args);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    我们都知道,想要使用 OpenFeign,就需要在启动类中,使用注解EnableFeignClients,而该注解就是一切的开始。

    二、EnableFeignClients 的源码分析

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Import(FeignClientsRegistrar.class)
    public @interface EnableFeignClients {
    
    	String[] value() default {};
    	
    	String[] basePackages() default {};
    	
    	Class<?>[] basePackageClasses() default {};
    	
    	Class<?>[] defaultConfiguration() default {};
    	
    	Class<?>[] clients() default {};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    三、Import FeignClientsRegistrar 的作用

    首先它实现了接口ImportBeanDefinitionRegistrar。这个方法会被自动执行到。
    在这里插入图片描述

    	@Override
    	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    		// 注册配置信息
    		registerDefaultConfiguration(metadata, registry);
    		// 注册FeignClient接口
    		registerFeignClients(metadata, registry);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注册配置信息的,我们暂且先不去分析。直接先看看registerFeignClients(metadata, registry) 的具体实现。

    四、FeignClientsRegistrar#registerFeignClients(…)

    public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    		LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
    		// 获取EnableFeignClients注解的属性值
    		Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
    		final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
    		if (clients == null || clients.length == 0) {
    			// 获取包扫描器
    			ClassPathScanningCandidateComponentProvider scanner = getScanner();
    			scanner.setResourceLoader(this.resourceLoader);
    			// 增加扫描过滤,只获取带有FeignClient注解的
    			scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
    			// 解析注解EnableFeignClients的属性,获取属性中指定的扫描包信息
    			Set<String> basePackages = getBasePackages(metadata);
    			// 执行扫描指定的所有包
    			for (String basePackage : basePackages) {
    				candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
    			}
    		}
    		else {
    			for (Class<?> clazz : clients) {
    				candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
    			}
    		}
    
    		for (BeanDefinition candidateComponent : candidateComponents) {
    			if (candidateComponent instanceof AnnotatedBeanDefinition beanDefinition) {
    				// 强制校验,使用FeignClient注解的,只能是一个接口
    				AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
    				Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
    
    				// 获取FeignClient的属性值
    				Map<String, Object> attributes = annotationMetadata
    						.getAnnotationAttributes(FeignClient.class.getCanonicalName());
    
    				String name = getClientName(attributes);
    				String className = annotationMetadata.getClassName();
    				// 注册配置信息
    				registerClientConfiguration(registry, name, className, attributes.get("configuration"));
    				// 注册FeignClient,主要是界定是否是懒加载,进行特殊处理
    				registerFeignClient(registry, annotationMetadata, attributes);
    			}
    		}
    	}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    五、饥饿注册&懒注册 FeignClientsRegistrar#registerFeignClient(…)

    本文只分析饥饿注册模式。

    	private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
    			Map<String, Object> attributes) {
    		String className = annotationMetadata.getClassName();
    		if (String.valueOf(false).equals(
    				environment.getProperty("spring.cloud.openfeign.lazy-attributes-resolution", String.valueOf(false)))) {
    			// 饥饿注册
    			eagerlyRegisterFeignClientBeanDefinition(className, attributes, registry);
    		}
    		else {
    			// 懒注册
    			lazilyRegisterFeignClientBeanDefinition(className, attributes, registry);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    然后我们来看看饥饿注册时,是如何处理的。

    private void eagerlyRegisterFeignClientBeanDefinition(String className, Map<String, Object> attributes,
    			BeanDefinitionRegistry registry) {
    		// 校验属性,即校验FeignClient属性是否正确使用
    		validate(attributes);
    		// 获取FeignClientFactoryBean的BeanDefinition建造器
    		BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
    		
    		// 处理FeignClient的属性
    		// 此处省略若干代码...
    		
    		// 通过建造器获取到一个bean描述器
    		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
    		
    		// 此处省略若干代码...
    
    		// 包装bean描述器,获得一个holder实例,holder有beanDefinition, className, qualifiers 这3个属性
    		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
    		// 通过注册器注册FeignClientFactoryBean的beanDefinition
    		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    		registerRefreshableBeanDefinition(registry, contextId, Request.Options.class, OptionsFactoryBean.class);
    		registerRefreshableBeanDefinition(registry, contextId, RefreshableUrl.class, RefreshableUrlFactoryBean.class);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    六、通过Holder真正注册beanDefinition

    BeanDefinitionReaderUtils#registerBeanDefinition(...)的源码如下:

        public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
            String beanName = definitionHolder.getBeanName();
    		// 注册beanDefinition
            registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
            String[] aliases = definitionHolder.getAliases();
            if (aliases != null) {
                String[] var4 = aliases;
                int var5 = aliases.length;
    
                for(int var6 = 0; var6 < var5; ++var6) {
                    String alias = var4[var6];
                    registry.registerAlias(beanName, alias);
                }
            }
    
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    至此,成功的将FeignClient注解对应的接口,转变为FeignClientFactoryBeanBeanDefinition,并且将其放入Spring容器中。

    附录

    附1:图解本文

    在这里插入图片描述

    附2:本系列其他文章

    SpringCloud原理-OpenFeign篇(一、Hello OpenFeign项目示例)

  • 相关阅读:
    Spring Boot面试必问:启动流程
    华为交换机端口 access、trunk和hybrid收发数据规则
    linux 邮箱配置
    《012.SpringBoot+vue之在线考试系统》【前后端分离&有开发文档】
    暴力破解ssh/rdp/mysql/smb服务
    DoLa:对比层解码提高大型语言模型的事实性
    虚方法与抽象方法区别
    Git命令总结
    ElasticSearch 创建索引超时(ReadTimeoutError)
    抓包工具fiddler的基础知识
  • 原文地址:https://blog.csdn.net/FBB360JAVA/article/details/134531137