远程调用组件中的动态注册BeanDefinition解析(二)

lili_lu 发表于 2021/06/04 18:33:55 2021/06/04
【摘要】 Feign是一个声明式RESTful HTTP请求客户端,它使得编写Web服务客户端更加方便和快捷。使用Feign创建一个接口并使用Feign提供的注解修饰该接口,然后就可以使用该接口进行RESTful HTTP请求的发送。Feign还可以集成Ribbon和Eureka来为自己提供负载均衡和断路器的机制。接着上一篇,我们继续讲解动态注册BeanDefinition`。registerFeig...

Feign是一个声明式RESTful HTTP请求客户端,它使得编写Web服务客户端更加方便和快捷。使用Feign创建一个接口并使用Feign提供的注解修饰该接口,然后就可以使用该接口进行RESTful HTTP请求的发送。Feign还可以集成Ribbon和Eureka来为自己提供负载均衡和断路器的机制。

接着上一篇,我们继续讲解动态注册BeanDefinition`。

registerFeignClients函数中有一些实现上的细节值得大家认真了解并学习的,有利于大家对Spring框架的深度了解。首先是如何自定义Spring类扫描器。也就是如何使用ClassPathScanningCandidateComponentProvider的自定义和各类TypeFilter`。

ClassPathScanningCandidateComponentProvider的基本原理为:

  • 遍历basePackages,根据每个basePackage找出这个包下的所有的class。比如basePackage为com/test,会找出com.test包下所有的class。找出之后封装成Resource接口集合,这个Resource接口是Spring对资源的封装,有FileSystemResourceClassPathResourceUrlResource实现等。

  • 遍历找到的Resource集合,通过includeFilters和excludeFilters判断是否解析。这里的includeFilters和excludeFilters是TypeFilter接口类型的集合,是ClassPathBeanDefinitionScanner内部的属性。TypeFilter接口是一个用于判断类型是否满足要求的类型过滤器。excludeFilters中只要有一个TypeFilter满足条件,这个Resource就会被过滤。includeFilters中只要有一个TypeFilter满足条件,这个Resource就不会被过滤。

  • 如果没有被过滤。把Resource封装
    ScannedGenericBeanDefinition添加到BeanDefinition结果集中,然后返回。

在上边的代码中,Feign就使用了AnnotationTypeFilter,来过滤出被@FeignClient修饰的类。而getScanner函数的具体实现如下所示

protected ClassPathScanningCandidateComponentProvider getScanner() {
	return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
		@Override
		protected boolean isCandidateComponent(
				AnnotatedBeanDefinition beanDefinition) {
			//判断beanDefinition是非内部类,否则直接返回false
			if (beanDefinition.getMetadata().isIndependent()) {
				//判断是否为接口类,并且所实现的接口只有一个,并且该接口是Annotation.否则直接返回true
				if (beanDefinition.getMetadata().isInterface()
						&& beanDefinition.getMetadata()
								.getInterfaceNames().length == 1
						&& Annotation.class.getName().equals(beanDefinition
								.getMetadata().getInterfaceNames()[0])) {
					try {
						Class<?> target = ClassUtils.forName(
								beanDefinition.getMetadata().getClassName(),
								FeignClientsRegistrar.this.classLoader);
						return !target.isAnnotation();
					}
					catch (Exception ex) {
						this.logger.error(
								"Could not load target class: "
										+ beanDefinition.getMetadata().getClassName(),
								ex);
					}
				}
				return true;
			}
			return false;
		}
	};
}

第二个细节实现是getBasePackages的实现,了解它有利于你更好的了解@EanbleFeignClients的各个属性配置。

protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
	Map<String, Object> attributes = importingClassMetadata
			.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());

	Set<String> basePackages = new HashSet<>();
	//basePackage包括value的属性值
	for (String pkg : (String[]) attributes.get("value")) {
		if (StringUtils.hasText(pkg)) {
			basePackages.add(pkg);
		}
	}
	//basePackage包括basePackages的属性值
	for (String pkg : (String[]) attributes.get("basePackages")) {
		if (StringUtils.hasText(pkg)) {
			basePackages.add(pkg);
		}
	}
	//basePacakge包括basePackageClasses的属性值的包名
	for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
		basePackages.add(ClassUtils.getPackageName(clazz));
	}
	//如果都为空,那么就将@EnableFeignClients所修饰的类的所在包为basePackage
	if (basePackages.isEmpty()) {
		basePackages.add(
				ClassUtils.getPackageName(importingClassMetadata.getClassName()));
	}
	return basePackages;
}

第三个细节是对被@FeignClient修饰的接口类FeignClient的Bean信息的处理。在registerFeignClient函数中,FeignFeignClientFactoryBean作为这些FeignClient的基础Bean信息注册到了Spring容器中,并且将相关的属性进行设置。

private void registerFeignClient(BeanDefinitionRegistry registry,
		AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
	String className = annotationMetadata.getClassName();
	//FeignClientFactoryBean是工厂类,用来生成bean
	BeanDefinitionBuilder definition = BeanDefinitionBuilder
			.genericBeanDefinition(FeignClientFactoryBean.class);
	validate(attributes);
	//将@FeignClient的属性设置到BeanDefinition的PropertyValue中。
	definition.addPropertyValue("url", getUrl(attributes));
	definition.addPropertyValue("path", getPath(attributes));
	String name = getName(attributes);
	definition.addPropertyValue("name", name);
	definition.addPropertyValue("type", className);
	definition.addPropertyValue("decode404", attributes.get("decode404"));
	definition.addPropertyValue("fallback", attributes.get("fallback"));
	definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
	definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

	String alias = name + "FeignClient";
	AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

	boolean primary = (Boolean)attributes.get("primary");

	beanDefinition.setPrimary(primary);

	String qualifier = getQualifier(attributes);
	if (StringUtils.hasText(qualifier)) {
		alias = qualifier;
	}

	BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
			new String[] { alias });
	//使用BeanDefinitionReaderUtils进行注册。
	BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

接下来将会关注实例初始化,FeignClientFactoryBean是工厂类,Spring容器通过调用它的getObject函数来获取对应的Bean实例。我们将会解析这部分的实现。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:cloudbbs@huaweicloud.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。