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下的自动配置类可以分为:
- spring mvc的web配置,包括servlet.DispatcherServletAutoConfiguration DispatcherServlet的配置、embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration 内置Servlet容器配置等等。
- data和jdbc配置,与访问数据库和缓存相关,包括redis.RedisAutoConfiguration redis配置,mongo.MongoDataAutoConfiguration mongo DB配置,jpa.JpaRepositoriesAutoConfiguration JPA数据库框架配置,jdbc.DataSourceAutoConfiguration jdbc数据源配置等等。
- 消息中间件配置,包括kafka.KafkaAutoConfiguration kafka配置,amqp.RabbitAutoConfiguration rabbit配置。
- 还有一些安全的认证相关、任务管理相关、http相关、缓存相关的一些配置,比较多,不好全面枚举。
就这样,spring boot通过加载配置文件中的配置,相对于使用@Configuration方法在全项目和依赖包中收集bean而言,更快地实现了依赖的bean注入和环境配置。至于各个AutoConfiguration类中的典型的代码逻辑,会在下一期代码中举例解释。因为一方面,这个涉及了内嵌的tomcat加载,其实属于另一部分内容。另一方面是这篇文章已经很长了,我实在坚持不下去了,需要另开一篇休息一下。