Spring-Cloud-OpenFeign源码解析-02-OpenFeign自动装配

@EnableFeignClients注解

java 复制代码
@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 {};

}

通过@Import注解导入FeignClientsRegistrar

java 复制代码
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
}

可以看到该类实现了ImportBeanDefinitionRegistrar接口

ImportBeanDefinitionRegistrar 接口允许开发者在运行时动态地向 Spring 应用程序上下文中注册 Bean 定义,这个接口通常与 @Import 注解结合使用,当 Spring 容器扫描到带有 @Import 注解的类时,会调用实现了ImportBeanDefinitionRegistrar接口的类registerBeanDefinitions方法。

开发者可以编写自定义逻辑来创建BeanDefinition对象(这些对象描述了如何创建Bean实例),并使用BeanDefinitionRegistry将它们注册到Spring容器中

FeignClientsRegistrar#registerBeanDefinitions()
java 复制代码
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 处理@EnableFeignClients注解上的属性配置,将配置注册到容器中
		registerDefaultConfiguration(metadata, registry);
        // 注册@FeignClient对应的接口,加入到容器中
		registerFeignClients(metadata, registry);
	}

FeignClientsRegistrar#registerFeignClients()

java 复制代码
	public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
        //获取@EnableFeignClients的属性集合
		Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
        //获取clients属性
		final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
            //拿到spring内置的扫描器
			ClassPathScanningCandidateComponentProvider scanner = getScanner();
			scanner.setResourceLoader(this.resourceLoader);
            //添加过滤器:过滤器所有被 @FeignClient 标记的接口
			scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
            //获取包的扫描路径
			Set<String> basePackages = getBasePackages(metadata);
			for (String basePackage : basePackages) {
             //扫描并添加到集合中
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
			}
		}
		else {
			for (Class<?> clazz : clients) {
				candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
			}
		}
		//循环处理@FeignClient接口
		for (BeanDefinition candidateComponent : candidateComponents) {
			if (candidateComponent instanceof AnnotatedBeanDefinition) {
				// verify annotated class is an interface
				AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
				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());
                //获取client的名称
				String name = getClientName(attributes);
				registerClientConfiguration(registry, name, attributes.get("configuration"));
                //核心方法,注入FeignClient对象
				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}

第一部分是扫描@Feignclient注解的接口类,第二部分就是对扫描出的接口类进行处理,主要关注registerClientConfigurationregisterFeignClient函数。

其中registerClientConfiguration是为了处理@FeignClient#configuration属性的,在这个函数会往Spring容器中添加#{serviceName}.FeignClientSpecification作为名字的FeignClientSpecification类对象

registerFeignClient函数则是将第一步扫描出来的接口注入到Spring容器中。

接下来我们看一下registerFeignClient函数

java 复制代码
	private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
			Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
		Class clazz = ClassUtils.resolveClassName(className, null);
		ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
				? (ConfigurableBeanFactory) registry : null;
		String contextId = getContextId(beanFactory, attributes);
		String name = getName(attributes);
        //创建FeignClientFactoryBean对象,下一篇会重点分析FeignClientFactoryBean
		FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
		factoryBean.setBeanFactory(beanFactory);
		factoryBean.setName(name);
		factoryBean.setContextId(contextId);
		factoryBean.setType(clazz);
		factoryBean.setRefreshableClient(isClientRefreshEnabled());
        //通过BeanDefinitionBuilder创建对象
		BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
            //设置feign接口参数,包括访问路径、fallback等
			factoryBean.setUrl(getUrl(beanFactory, attributes));
			factoryBean.setPath(getPath(beanFactory, attributes));
			factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
			Object fallback = attributes.get("fallback");
			if (fallback != null) {
				factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
						: ClassUtils.resolveClassName(fallback.toString(), null));
			}
			Object fallbackFactory = attributes.get("fallbackFactory");
			if (fallbackFactory != null) {
				factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
						: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
			}
			return factoryBean.getObject();
		});
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
		definition.setLazyInit(true);
		validate(attributes);
		//获取BeanDefinition对象
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
		beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
		beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);

		// has a default, won't be null
		boolean primary = (Boolean) attributes.get("primary");

		beanDefinition.setPrimary(primary);

		String[] qualifiers = getQualifiers(attributes);
		if (ObjectUtils.isEmpty(qualifiers)) {
			qualifiers = new String[] { contextId + "FeignClient" };
		}

		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
        //调用registerBeanDefinition方法注入Spring容器中
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);

		registerOptionsBeanDefinition(registry, contextId);
	}

看一下registerBeanDefinition方法

java 复制代码
	public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {

		// Register bean definition under primary name.
		String beanName = definitionHolder.getBeanName();
        //通过Spring提供的注册器将FeignClient接口包装成的BeanDefinition注入到容器中
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

		// Register aliases for bean name, if any.
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
	}

总结

通过@EnableFeignClients中的@ImportFeignClientsRegistrar配置,当Spring容器扫描到带有 @Import 注解的类时,会调用实现了ImportBeanDefinitionRegistrar接口的类registerBeanDefinitions方法。从而扫描配置包下面的所有@FeigntClient注解的接口,包装成BeanDefinition并注入到Spring容器中。

相关推荐
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck2 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei2 小时前
java的类加载机制的学习
java·学习
Yaml44 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~4 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616884 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7894 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
记录成长java5 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
睡觉谁叫~~~5 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust
程序媛小果5 小时前
基于java+SpringBoot+Vue的旅游管理系统设计与实现
java·vue.js·spring boot