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

lili_lu 发表于 2021/06/04 18:30:17 2021/06/04
【摘要】 前面讲到 FeignClientsRegistrar的registerBeanDefinitions函数主要做了两个事情,一是注册@EnableFeignClients提供的自定义配置类中的相关Bean信息,二是根据@EnableFeignClients提供的包信息扫描被@FeignClient修饰的FeignCleint接口类,然后进行注册。@EnableFeignClients的自定义...

前面讲到 FeignClientsRegistrarregisterBeanDefinitions函数主要做了两个事情,一是注册@EnableFeignClients提供的自定义配置类中的相关Bean信息,二是根据@EnableFeignClients提供的包信息扫描被@FeignClient修饰的FeignCleint接口类,然后进行注册。

@EnableFeignClients的自定义配置类就是被@Configuration注解修饰的配置类,它会提供用来组装FeignClient的各类组件的自定义实例。它们包括:Client,TargeterDecoderEncoderContract

private void registerDefaultConfiguration(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
	//获取到metadata中关于EnableFeignClients的属性值键值对。
	Map<String, Object> defaultAttrs = metadata
			.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
	//如果EnableFeignClients配置了defaultConfiguration类,那么才进行下一步操作,如果没有,会使用
	//默认的FeignConfiguration
	if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
		String name;
		if (metadata.hasEnclosingClass()) {
			name = "default." + metadata.getEnclosingClassName();
		}
		else {
			name = "default." + metadata.getClassName();
		}
		registerClientConfiguration(registry, name,
				defaultAttrs.get("defaultConfiguration"));
	}
}

registerDefaultConfiguration函数会判断@EnableFeignClientsdefaultConfiguration属性是否被赋值。如果有,则将调用registerClientConfiguration函数,进行BeanDefinitionRegistry的注册。

BeanDefinitionRegistrySpring框架中用于动态注册Bean信息的接口,调用其registerBeanDefinition函数可以将BeanDefinition注册到Spring容器中,name就是注册Bean的名称。

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
		Object configuration) {
	//使用BeanDefinitionBuilder来生成BeanDefinition并注册大registry上。
	BeanDefinitionBuilder builder = BeanDefinitionBuilder
			.genericBeanDefinition(FeignClientSpecification.class);
	builder.addConstructorArgValue(name);
	builder.addConstructorArgValue(configuration);
	registry.registerBeanDefinition(
			name + "." + FeignClientSpecification.class.getSimpleName(),
			builder.getBeanDefinition());
}

FeignClientSpecification类实现了NamedContextFactory.Specification接口,它是Feign组件示例化的重要一环,它会携带@EnableFeignClients中的defaultConfiguration值,供组件Bean初始化时使用。Spring Cloud通过NamedContextFactory来创建一些系列的子context,来让对应的Specification在这些子context中创建bean对象。这样使得各个子context中的Bean相互独立,互不影响,可以方便的通过子context管理一系列的Bean实例。NamedContextFactory有三个功能,一是创建AnnotationConfigApplicationContext子context;二是在子context中获取Bean实例;三是当子context消亡时清除其中的Bean信息。在Feign中,FeignContext继承了NamedContextFactory,是Feign中组件实例化的最为关键的类。下图就是FeginContext的相关类图。

FeignContext相关类图
image.png

FeignAutoConfigurationFeign的自动配置类,在其中生成了FeignContext实例。并且将之前注册的FeignClientSpecification通过setConfigurations函数设置给FeignContext实例。这里就处理了默认配置类FeignClientsConfiguration和自定义配置类的替换问题。如果FeignClientsRegistrar没有注册自定义配置类,那么configurations成员变量会是一个空队列。否则会在setConfigurations中进行默认配置类的替换。

//FeignAutoConfiguration.java
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
	FeignContext context = new FeignContext();
  //
	context.setConfigurations(this.configurations);
	return context;
}

//FeignContext.java
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
	public FeignContext() {
        //将默认的FeignClientConfiguration作为参数传递给构造函数
		super(FeignClientsConfiguration.class, "feign", "feign.client.name");
	}
}
//NamedContextFactory.java
public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
        String propertyName) {
    this.defaultConfigType = defaultConfigType;
    this.propertySourceName = propertySourceName;
    this.propertyName = propertyName;
}
//对于FeignContext,List<C>就是指List<FeignClientSpecification>
public void setConfigurations(List<C> configurations) {
		for (C client : configurations) {
			this.configurations.put(client.getName(), client);
		}
}

NamedContextFactorycreateContext会创建具有名称的SpringAnnotationConfigApplicationContext作为当前Context的child。这些AnnotationConfigApplicationContext会被用来管理Feign组件的不同实例。

protected AnnotationConfigApplicationContext createContext(String name) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    //获取该name所对应的configuration,如果有的话,就注册都子context中。
    if (this.configurations.containsKey(name)) {
        for (Class<?> configuration : this.configurations.get(name)
                .getConfiguration()) {
            context.register(configuration);
        }
    }
    //注册default的Configuration,也就是FeignClientsRegistrar类的registerDefaultConfiguration函数中注册的Configuration
    for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
        if (entry.getKey().startsWith("default.")) {
            for (Class<?> configuration : entry.getValue().getConfiguration()) {
                context.register(configuration);
            }
        }
    }
    //注册PropertyPlaceholderAutoConfiguration和FeignClientsConfiguration配置类
    context.register(PropertyPlaceholderAutoConfiguration.class,
            this.defaultConfigType);
    //设置子context的Environment的propertySource属性源
    //propertySourceName = feign; propertyName = feign.client.name
    context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
            this.propertySourceName,
            Collections.<String, Object> singletonMap(this.propertyName, name)));
    //所有context的parent都相同,这样的话,一些相同的Bean可以通过parent context来获取。
    if (this.parent != null) {
        context.setParent(this.parent);
    }
    context.setDisplayName(generateDisplayName(name));
    context.refresh();
    return context;
}

而由于NamedContextFactory实现了DisposableBean接口,当该bean消亡时,会清除掉自己创建的所有子context。

@Override
	public void destroy() {
		Collection<AnnotationConfigApplicationContext> values = this.contexts.values();
		for (AnnotationConfigApplicationContext context : values) {
			context.close();
		}
		this.contexts.clear();
	}

NamedContextFactory会创建出以name作为唯一标识的AnnotationConfigApplicationContext实例,然后每个AnnotationConfigApplicationContext实例都会register一些配置类,从而可以给出一系列的基于配置类生成的Bean实例,这样,我们就可以基于name来管理一系列的配置类Bean实例,从而可以为不同的FeignClient准备不同配置Bean实例,比如说DecoderEncoder之类的。这些我们会在后续的讲解中详细介绍。

FeignClientsRegistrar做的第二件事情是扫描package并且注解被@FeignClient修饰的接口类的bean信息。

public void registerFeignClients(AnnotationMetadata metadata,
		BeanDefinitionRegistry registry) {
	//1 生成自定义的ClassPathScanningProvider
	ClassPathScanningCandidateComponentProvider scanner = getScanner();
	scanner.setResourceLoader(this.resourceLoader);

	Set<String> basePackages;
	//获取EnableFeignClients所有属性的键值对
	Map<String, Object> attrs = metadata
			.getAnnotationAttributes(EnableFeignClients.class.getName());
    //依照Annotation来进行TypeFilter,只会扫描出被FeignClient修饰的类
	AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
			FeignClient.class);

	final Class<?>[] clients = attrs == null ? null
			: (Class<?>[]) attrs.get("clients");
	//2.1 如果没有设置clients属性,那么需要扫描basePackage,所以设置了AnnotationTypeFilter,并且去获取basePackage。
	if (clients == null || clients.length == 0) {
		scanner.addIncludeFilter(annotationTypeFilter);
		basePackages = getBasePackages(metadata);
	}
	//2.2 如果设置了Clients属性,那么久不在进行basePackage的扫描,而是直接使用这些类所在的package
	else {
		final Set<String> clientClasses = new HashSet<>();
		basePackages = new HashSet<>();
		//获取clients的值中的class信息,并将其所在package名称存储到basePackages中。
		for (Class<?> clazz : clients) {
			basePackages.add(ClassUtils.getPackageName(clazz));
			clientClasses.add(clazz.getCanonicalName());
		}
		//添加对这些类的Filter设置
		AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
			@Override
			protected boolean match(ClassMetadata metadata) {
				String cleaned = metadata.getClassName().replaceAll("\\$", ".");
				return clientClasses.contains(cleaned);
			}
		};
		scanner.addIncludeFilter(
				new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
	}
	//3 遍历上述过程中获取的basePackages列表
	for (String basePackage : basePackages) {
		//4.1 获取basepackage下的所有BeanDefinition
		Set<BeanDefinition> candidateComponents = scanner
				.findCandidateComponents(basePackage);
		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");
				//4.2 从这些BeanDefinition中获取到FeignClient的属性值
				Map<String, Object> attributes = annotationMetadata
						.getAnnotationAttributes(
								FeignClient.class.getCanonicalName());
				String name = getClientName(attributes);
				//4.3 对于单独某个FeignClient的configuration进行配置
				registerClientConfiguration(registry, name,
						attributes.get("configuration"));
				//4.4 注册FeignClient的BeanDefinition
				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}
}

registerFeignClients函数就是依据@EnableFeignClients的属性来获取basePackages,然后获取这些包下的所有BeanDefinition,获取修饰这些类的@FeignClient的属性,来注册动态的Bean信息。

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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