Spring 自动装配[1.3]ImportBDRegistrar

1,328 阅读4分钟

这是我参与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实例化的哪个步骤被调用的呢?