自动装配[1.4] AutoConfigurationImportSelector

1,537 阅读4分钟

这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战

前言

在前面我们解析了:

  • @Import注解的引入是如何实现的:
    • @Import注解将指定的ImportBDRegistrar接口存放到缓存中。
    • 在调用到某个位置的时候,调用这些importRegistrar的registrarBD方法,将这些实现类中指定的BD实例化并注入到context中。

但这并没有解决我们的问题:

  • 自动注入到底是咋实现的?

虽然根据之前的@Import注解的分析,大部分自动注入都可以通过在**@EnableXXX注解里import对应的ImportBDRegistrar**来做到对应的方式,然而看看这个:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface EnableEurekaClient

这里属于啥都没有,这种又是怎么实现的呢?

AutoConfigurationImportSelector

还记得之前在EnableAutoConfiguration中的这个东西吗?

@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration 

回顾一下在processImport方法中,importSelector是如何解析的:

graph LR
getImports获取Import写的内容-->一堆import的SourceClass-->实例化importSelector
实例化importSelector-->importSelector.selectImport-->getImports获取Import写的内容

如果是deferredImportSelector,那么前面的有失偏颇,处理流程实际上是:

graph LR
getImports获取Import写的内容-->一堆import的SourceClass-->实例化deferredImportSelector
实例化deferredImportSelector-->保存对应的DeferredImportSelectorGrouping并等待调用
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);

		public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
			DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
			if (this.deferredImportSelectors == null) {
				DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
				handler.register(holder);
				handler.processGroupImports();
			}
			else {
				this.deferredImportSelectors.add(holder);
			}
		}
		
				public void register(DeferredImportSelectorHolder deferredImport) {
			Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();
			DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
					(group != null ? group : deferredImport),
					key -> new DeferredImportSelectorGrouping(createGroup(group)));
			grouping.add(deferredImport);
			this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
					deferredImport.getConfigurationClass());
		}

//而这些grouping的处理,在这里:
		public void processGroupImports() {
			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
				Predicate<String> exclusionFilter = grouping.getCandidateFilter();
                //注意看这里
				grouping.getImports().forEach(entry -> {
					ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
					try {
						processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
								Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
								exclusionFilter, false);
					}
					catch (BeanDefinitionStoreException ex) {
						throw ex;
					}
					catch (Throwable ex) {
						throw new BeanDefinitionStoreException(
								"Failed to process import candidates for configuration class [" +
										configurationClass.getMetadata().getClassName() + "]", ex);
					}
				});
			}
		}

//这个getImports,不是importSelectors的,而是group的:
		public Iterable<Group.Entry> getImports() {
			for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
				this.group.process(deferredImport.getConfigurationClass().getMetadata(),
						deferredImport.getImportSelector());
			}
            //【A】
			return this.group.selectImports();
		}

那么我们就来看看这个类里对应的group的process:

		public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
			Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
					() -> String.format("Only %s implementations are supported, got %s",
							AutoConfigurationImportSelector.class.getSimpleName(),
							deferredImportSelector.getClass().getName()));
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}

对应debug,打完断点发现在这一行拿到了很多autoConfigurationEntry:

AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
      .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);

其中就包括了我们之前提到的eureka的大部分Configuration。

根据这边往上看就知道,这些配置类会被放到processImport方法中执行,这样子和之前的自动装配[1.1],[1.2],[1.3]就对上了:

//【A】的地方调用了!
public Iterable<Entry> selectImports() {
   if (this.autoConfigurationEntries.isEmpty()) {
      return Collections.emptyList();
   }
   Set<String> allExclusions = this.autoConfigurationEntries.stream()
         .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
   Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
         .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
         .collect(Collectors.toCollection(LinkedHashSet::new));
   processedConfigurations.removeAll(allExclusions);

   return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
         .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
         .collect(Collectors.toList());
}

看到这里对整个流程就更清楚一点了:

  • 我们可以通过@Import的方式,手动注入一些BDRegistrar
  • 也可以借助这里的AutoConfigurationImportSelector.AutoConfigurationGroup的这个process方法中的getAutoConfigurationEntry,附带到ConfigurationClassParser中,来做注入的工作。

通过断点可以看到,采取第二种方法的中间件要更多一些,说明这里就是SpringBoot自动注入的关键。那么我们就看看这个方法:

getAutoConfigurationEntry

这个方法仍在AutoConfigurationImportSelector中

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
      AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
    //这里是获取annotationMetadata中,@AutoConfiguration中的注解
    //一般来说我们如果没有手动添加这个注解,这里拿出来的是两个String[0]
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
    //关键其实是在这一步
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    //下面就是一些去重啊以及事件发布了
   configurations = removeDuplicates(configurations);
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);
   configurations = filter(configurations, autoConfigurationMetadata);
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

//我们能获取到一大堆配置的关键
	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
				+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

//通过debug进来,发现这里result并不是空,并且包含了我们所需的所有configuration
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}
//关键就在这里,会根据classLoader,去解析对应包中,META-INF/spring.factories中的配置信息,并装载到缓存中
		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
            //而这个缓存,仅在这里做put操作
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}
  • 这里的annotationMetadata指的是启动类上加的注解。

在Spring.factories中可以看到对应的信息,这下就清楚了:

//open-feign-core包中
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration

也就是说,最终还是通过这里来装载的。这下,自动装配的原理就清楚了:其实就是让Spring和中间件,来帮助我们把对应的Configuration注入到包中。

不妨来看看,这些自动注入的都是些什么东西,可以发i西安就是一些中间件中底层的bean。

总结

看到这里就大概明白,自动装配是怎么做的了,流程图大致如下:

www.processon.com/diagraming/…

看到这里也可以对之前的结论做一个补充:

  • 显式的@Import(ImportBDRegistrar),一般是用于像feign一样需要对业务代码中的接口做代理的(例如:mybatis,openFeign),在这里会通过这些包中指定的规则,来生成对应的代理类。
  • 如果不是@Import的,一般就是导入一些AutoConfiguration,这些大部分在对应中间件包中以AutoConfiguration作为后缀,并且必须在对应包中的META-INF/spring.factories声明。
    • 这部分的内容,依靠AutoConfigurationImportSelector来导入到容器中。

这里还有一些遗留的问题:

  • ClassLoader,是如何装载类的,Spring中呢?
  • 这些自动装配的类,是在哪个节点上被写入的?