这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战」
前言
在前面的分析中我们知道了:
- 只要是@Import,那么最后执行的必然是ImportBeanDefinitionRegistrar,否则就会往下继续递归,直到都是这个类为止。
这里我们来处理之前遗留的:
类:【ImportBeanDefinitionRegistrar】
- 子类如何实现的?
- 何处处理的?
- configurationClass类中,loadBeanDefinitionsForConfigurationClass在哪里,何时被调用?
子类实现及接口定义
先来看看接口中给出的方法:
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
复制代码
光从这里也看不出什么门道,只知道这里重载了两个方法,并且上面的方法调用了下面的方法,而且没用到上面的这个bean名称构造器。
从引用的地方也看出来上面重写的委实不多,重写类就俩,而这俩和SpringData关系密切:
- AbstractRepositoryConfigurationSourceSupport
- RepositoryBeanDefinitionRegistrarSupport
- AbstractRepositoryConfigurationSourceSupport
这个类也不是具体的实现类,而是一个抽象类,但这个类基本上可以说提供了大部分数据存储数据和SpringData整合的功能:
public abstract class AbstractRepositoryConfigurationSourceSupport implements ImportBeanDefinitionRegistrar, BeanFactoryAware, ResourceLoaderAware, EnvironmentAware { } 复制代码
因此我们重点就在下面这个方法的子类重写,看看干啥了。
在看子类方法重写之前,先看看这个方法的注释:
Register bean definitions as necessary based on the given annotation metadata of the importing @Configuration class. Note that BeanDefinitionRegistryPostProcessor types may not be registered here, due to lifecycle constraints related to @Configuration class processing. 复制代码
简单点说,这个方法根据导入@Configuration类的给定注释元数据,根据需要注册bean定义。
记得开头说的吗?我们举例子就都拿OpenFeign来看了。
FeignClientsRegisrar
这里需要注意的一点是,这个类在EnableFeignClients注解里是被@Import导入到Spring容器中的:
@Import(FeignClientsRegistrar.class) public @interface EnableFeignClients { //..... } 复制代码
直接一点来看上面子类的重写方法:
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
复制代码
注册默认配置
registerDefaultConfiguration(metadata, registry);
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//这里的metadata打断点进来可以看到,其实指的是启动的类,以及收集起来的那些通过feignClient注解所标注的
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
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"));
}
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
复制代码
这里的代码通篇看下来没什么难度:
- configuration指的其实是@EnableFeignClients里指定的defaultConfiguration,如果我们没配那就没有咯。
- 流程上,这里就是根据给定的metadata,构建一个BeanDefinition放入context中。这里的beanDefinition,对应的是在启动类标注的@EnableFeignClients注解中对应的配置。如果没有配置,其实这个Configuration没啥用(因为虽然会放到容器中,但里面的属性全是空)。
注册feign类
在这个方法中,会将包下所有通过**@FeignClient**标注的接口,在这里初始化。
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//这个canner是一个根据classpath去扫描的
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
//这里是需要扫描的包的顶级目录
Set<String> basePackages;
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
//如果这里从enable的注解上找不到写的clients,那就根据metadata,找对应的顶级目录的名称
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
else {
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
basePackages.add(ClassUtils.getPackageName(clazz));
clientClasses.add(clazz.getCanonicalName());
}
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)));
}
//这里就是根据顶级目录的包,去一一查找对应写了feignClient的接口,把这些接口的信息变成BD放到容器中
//这里的feignClient,对应的beanDefinition是通过FeignClientFactoryBean来的
for (String basePackage : basePackages) {
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");
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//这个方法里就是转化并注册,里面比较有意思的一点是还会去初始化configuration
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
复制代码
小结
在这里能看到:
-
实际上,这些ImportBDRegistrar,是以指定模式接收到参数,并将对应的概念、信息(比如上面的feignClients),变成BeanDefinition,注册到容器中的。
这里可能涉及到扫描、FactoryBean等相关内容。
-
这里可以往下继续深究的点,包括:
- feign的beanDefinition既然实际上是以FeignClientFactoryBean的形式注册的,那么是如何调用的呢?
- 这个BDRegistrar,是在bean实例化的哪个步骤被调用的呢?