spring boot原理分析(三):项目依赖包中bean的自动配置1

  • 前言
  • 自动配置注解@EnableAutoConfiguration
  • 环境上下文:基础包配置
  • 自动化配置类的导入
  • 获取自动配置类的依赖信息
  • 获取能够加载的自动配置类


前言

    spring boot原理分析(二)主要是介绍了@SpringBootConfiguration注解所包含的@SpringBootConfiguration和@ComponentScan这两个注解,@SpringBootConfiguration将启动类定义为一个java配置类,而@ComponentScan的作用就是将项目本地目录下的bean注入到IoC容器中,那么外部引入的依赖包中的bean是如何引入到项目中的呢?这个工作主要是由@SpringBootConfiguration下的最后一个注解@EnableAutoConfiguration实现的。

自动配置注解@EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	Class<?>[] exclude() default {};

	String[] excludeName() default {};

}

    @EnableAutoConfiguration注解中定义了exclude参数和excludeName参数用来排除不想注入的类,这很容易理解。其实比较重要的地方在@AutoConfigurationPackage注解和@Import(AutoConfigurationImportSelector.class)。

环境上下文:基础包配置

    @AutoConfigurationPackage注解使用@Import用来引入AutoConfigurationPackages.Registrar.class。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

Registrar实现了ImportBeanDefinitionRegistrar接口,这个接口的实现类可以由@Import注解引入,会自动调用它的registerBeanDefinitions方法。registerBeanDefinitions方法传入了用来获取注解信息的AnnotationMetadata和用来注册bean的BeanDefinitionRegistry。ImportBeanDefinitionRegistrar这个接口可以用来注入bean或者实现自己定义的类@Component注解。

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

  @Override
  public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    register(registry, new PackageImport(metadata).getPackageName());
  }

  @Override
  public Set<Object> determineImports(AnnotationMetadata metadata) {
    return Collections.singleton(new PackageImport(metadata));
  }

}

而在这里主要的作用是将启动类的包加入到命名为BasePackeges的基础包中,同时将存储这些基础包的BasePackeges类注册为一个名为org.springframework.boot.autoconfigure.AutoConfigurationPackages的bean。上面代码中通过new PackageImport(metadata).getPackageName()获取了启动类的包名。在register方法中,会先判断命名为AutoConfigurationPackages的bean是否被注册,

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
  if (registry.containsBeanDefinition(BEAN)) {
    BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
    ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
    constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
  }
  else {
    GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
    beanDefinition.setBeanClass(BasePackages.class);
    beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    registry.registerBeanDefinition(BEAN, beanDefinition);
  }
}

如果没有注册,就新建一个BeanDefinition。java中的类会有一个相应的Class对象,同样的,BeanDefinition就类似于是bean的“Class”,用来存储bean的相关信息。代码中关联了这个BeanDefinition对应的bean是BasePackages类。BasePackages类内部其实只是包含了一个String的list,用来存储所有基础包的完整包名。

static final class BasePackages {

  private final List<String> packages;

  private boolean loggedBasePackageInfo;

  BasePackages(String... names) {
    List<String> packages = new ArrayList<>();
    for (String name : names) {
      if (StringUtils.hasText(name)) {
        packages.add(name);
      }
    }
    this.packages = packages;
  }
  ......
}

可以通过获取构造函数的参数,将需要加入新的包名设置进去,这也是beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames)所做的事情。最后通过BeanDefinitionRegistry注册了这个名为org.springframework.boot.autoconfigure.AutoConfigurationPackages的BasePackages类的bean。这样,就可以通过获取这个bean获得所有基础包的包名。如果发现这个bean已经被注册,就需要获取这个BeanDefinition,然后把启动类的包名加入到所有基础包里。这就是@AutoConfigurationPackage所做的全部工作。

自动化配置类的导入

    @Import(AutoConfigurationImportSelector.class)是@Import一种特殊用法。@Import可以传入一个ImportSelector接口的实例,这个接口只有一个方法String[] selectImports(AnnotationMetadata importingClassMetadata),可以返回一个由不同类名组成的String数组,这些类能够作为bean被@Import注入到IoC容器中。AutoConfigurationImportSelector类实现了DeferredImportSelector。DeferredImportSelector接口是ImportSelector的拓展,官方文档的解释是

A variation of ImportSelector that runs after all @Configuration beans have been processed. This type of selector can be particularly useful when the selected imports are @Conditional

,即当所有@Configuration配置的bean被注入之后,这个DeferredImportSelector的selectImports才会被执行,这样就可以实现注入某些需要依赖特定的bean作为参数才能成功构造的这类bean。

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	}
	AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
			.loadMetadata(this.beanClassLoader);
	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
			annotationMetadata);
	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

    selectImports方法的最终返回的结果是需要注入的bean的完整类名(包括包名),为了完成这个目标,selectImports方法分为两步。AutoConfigurationMetadataLoader.loadMetadata()用来加载所有bean注入的依赖信息;getAutoConfigurationEntry方法用来加载依赖包中注入bean的信息,然后根据依赖信息过滤掉因不满足条件而不能注入的bean。

获取自动配置类的依赖信息

    首先看AutoConfigurationMetadataLoader.loadMetadata()方法。

protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";

public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
	return loadMetadata(classLoader, PATH);
}

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
	try {
		Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
				: ClassLoader.getSystemResources(path);
		Properties properties = new Properties();
		while (urls.hasMoreElements()) {
			properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
		}
		return loadMetadata(properties);
	}
	catch (IOException ex) {
		throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
	}
}

这个类中比较重要的是定义了一个字符串,用来存储“spring-autoconfigure-metadata.properties”文件名,这个文件可以在spring-boot-autoconfigure jar包中的META-INF/目录下找到,主要是用来替代类似@OnClassCondition的注解。下面的截取的文件内容中,可以看到Configuration配置为默认值,AutoConfigureAfter配置就代表需要依赖某些bean先行加载。所有这些配置和依赖都可以在这个文件中加载,加快了spring boot的启动速度。

#Thu Sep 05 14:08:42 GMT 2019
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.data.neo4j.Neo4jBookmarkManagementConfiguration.Configuration=
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration=
org.springframework.boot.autoconfigure.kafka.KafkaAnnotationDrivenConfiguration.Configuration=
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration.ConditionalOnClass=org.influxdb.InfluxDB
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration=
org.springframework.boot.autoconfigure.session.SessionRepositoryFilterConfiguration.Configuration=
org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration=
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration.AutoConfigureBefore=org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.freemarker.FreeMarkerReactiveWebConfiguration.Configuration=
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration.Configuration=
······

loadMetadata方法将这些配置使用PropertiesLoaderUtils.loadProperties方法加载到AutoConfigurationMetadata,供后续筛选不满足加载条件的bean时使用。PropertiesLoaderUtils.loadProperties也是一个官方提供的很方便的用来加载properties文件的工具。

获取能够加载的自动配置类

    然后再看getAutoConfigurationEntry方法。

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
		AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	}
	//1. 获取@EnableAutoConfiguration的参数值
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	//2. 获取各种外部依赖的AutoConfiguration配置类名
	List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
	//3. 移除重复的配置类
	configurations = removeDuplicates(configurations);
	//4. 获取需要(exclude,excludeName)移除的配置类
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
	//5. 检查需要移除的配置类是否在候选配置列表中
	checkExcludedClasses(configurations, exclusions);
	//6. 移除配置类
	configurations.removeAll(exclusions);
	//7. 过滤掉不满足加载条件的配置类
	configurations = filter(configurations, autoConfigurationMetadata);
	//8. 将自动配置的类打包成事件通知发送
	fireAutoConfigurationImportEvents(configurations, exclusions);
	return new AutoConfigurationEntry(configurations, exclusions);
}

可以看到getAutoConfigurationEntry的过程中,最重要的是第2步,获取各种外部依赖的AutoConfiguration配置类名。getCandidateConfiguration方法中调用了

List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());

。这方法实际传入的是EnableAutoConfiguration.class和beanClassLoader这两个参数。这里需要了解下SpringFactoriesLoader.loadFactoryNames方法具体做了什么。

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

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
	//获取缓存数据
	MultiValueMap<String, String> result = cache.get(classLoader);
	if (result != null) {
		return result;
	}

	try {
		//获取META-INF/spring.factories文件路径
		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);
			//获取spring.factories文件内容
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
			for (Map.Entry<?, ?> entry : properties.entrySet()) {
				String factoryClassName = ((String) entry.getKey()).trim();
				for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
					result.add(factoryClassName, factoryName.trim());
				}
			}
		}
		//将配置存入缓存
		cache.put(classLoader, result);
		return result;
	}
	catch (IOException ex) {
		throw new IllegalArgumentException("Unable to load factories from location [" +
				FACTORIES_RESOURCE_LOCATION + "]", ex);
	}
}

FACTORIES_RESOURCE_LOCATION是一个常量字符串,存储的是“META-INF/spring.factories”。loadSpringFactories方法会获取所有spring.factories文件的路径,然后同样使用PropertiesLoaderUtils.loadPropertie将文件中的配置信息进行加载。这个方法会被多次调用,因此加载后的配置也会被存储到缓存中。比较神奇的是SpringFactoriesLoader类是存放在spring-core的jar包中,这意味着加载spirng.factories文件的代码逻辑是spring域的内容,任何一个基于spring的应用(不只是spring-boot的),都可以使用这个类加载spirng.factories文件的配置(只要你配置了这个文件)。

# 初始化工具
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# 应用监听器
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# 配置监听器
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# 自动配置的过滤组件
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# 自动配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
······

    上面列出了spring-boot jar下spring.factories中的部分内容,可以将配置内容分为初始化工具、应用的监听器、配置的监听器、自动配置的过滤组件、自动配置类、失败分析器和可获取的template提供者等。自动配置类可以完成外部bean的有依赖的加载,spring boot下的自动配置类可以分为:

  1. spring mvc的web配置,包括servlet.DispatcherServletAutoConfiguration DispatcherServlet的配置、embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration 内置Servlet容器配置等等。
  2. data和jdbc配置,与访问数据库和缓存相关,包括redis.RedisAutoConfiguration redis配置,mongo.MongoDataAutoConfiguration mongo DB配置,jpa.JpaRepositoriesAutoConfiguration JPA数据库框架配置,jdbc.DataSourceAutoConfiguration jdbc数据源配置等等。
  3. 消息中间件配置,包括kafka.KafkaAutoConfiguration kafka配置,amqp.RabbitAutoConfiguration rabbit配置。
  4. 还有一些安全的认证相关、任务管理相关、http相关、缓存相关的一些配置,比较多,不好全面枚举。

    就这样,spring boot通过加载配置文件中的配置,相对于使用@Configuration方法在全项目和依赖包中收集bean而言,更快地实现了依赖的bean注入和环境配置。至于各个AutoConfiguration类中的典型的代码逻辑,会在下一期代码中举例解释。因为一方面,这个涉及了内嵌的tomcat加载,其实属于另一部分内容。另一方面是这篇文章已经很长了,我实在坚持不下去了,需要另开一篇休息一下。