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注解的接口类,第二部分就是对扫描出的接口类进行处理,主要关注registerClientConfiguration和registerFeignClient函数。

其中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中的@Import和FeignClientsRegistrar配置,当Spring容器扫描到带有 @Import 注解的类时,会调用实现了ImportBeanDefinitionRegistrar接口的类registerBeanDefinitions方法。从而扫描配置包下面的所有@FeigntClient注解的接口,包装成BeanDefinition并注入到Spring容器中。

相关推荐
心中有你02141 小时前
Java实现Dijkstra最短路径算法(邻接表 + 优先队列)
java·数据库·算法
步行cgn2 小时前
Spring MVC + Thymeleaf 模板解析失败:从 500 错误到页面正常显示
java·spring·mvc
lgcgkCQ2 小时前
【AI 转型实战 1】SpringAI 入门:搭建基础大模型对话能力
java·ai·agent·rag·spring ai·mcp·langchain4j
Wang's Blog2 小时前
Java 项目实战:交友项目 - 用户体系集成总结
java·开发语言·交友
学到老活到老3 小时前
第 24 集:配置文件写了 80,Spring Boot 为什么读到 90?
java·后端·spring
tongyiixiaohuang3 小时前
供应商主数据同步实战:从易快报到金蝶云星空的单一策略落地
java·网络·数据库
鱼宵4 小时前
取集合第一个元素,Java 为什么要写三种写法?
java·windows·python·java21
学长毕设4 小时前
基于SpringBoot的餐厅管理系统-附源码
java·spring boot·后端
刚入门的大一新生4 小时前
Linux-线程互斥
java·开发语言
赵小哥要冲啦5 小时前
社区邻里互助小程序从 0 到 1 复盘:需求拆解与信任机制设计
java·小程序·需求分析