自动配置:根据添加的jar包依赖,会自动将一些配置类的bean注册进IOC容器,可以在需要的地方使用@Autowired或者@Resource等注解使用它。
Spring Boot如何自动配置的,都把哪些组件进行了自动配置?
springboot应用的启动入口是@SpringBootApplication注解标注类中的main()方法,@SpringBootApplication:springboot应用标注在某个类上说明这个类是springboot的主配置类,springboot就应该运行这个类的main()方法启动springboot应用。
查看@SpringBootApplication内部源码进行分析,核心代码具体如下:
@Target(ElementType.TYPE)//注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME)//表示注解的生命周期,Runtime运行时
@Documented//表示注解可以记录在javadoc中
@Inherited//表示可以被子类继承该注解
@SpringBootConfiguration//表明该类为配置类
@EnableAutoConfiguration//启动自动配置功能
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
//根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
//根据classname来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全类名字符串数组
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
//指定扫描包,参数是包名的字符串数组
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
//扫描特定的包,参数类似是class类型数组
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
@SpringBootApplication注解是一个组合注解,前面4个是注解的元数据信息, 主要看后面3个注解。
@SpringBootConfiguration
springboot的配置类,标注在某个类上。
查看注解源码,如下所示
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration//配置类的作用等同于配置文件,配置类也是容器中的一个对象
public @interface SpringBootConfiguration {
}
从上述源码可以看出,注解内部有一个核心注解@Configuration,该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration是被SpringBoot进行了重新封装命名而已。
接着看下@EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//自动配置包
@AutoConfigurationPackage
//spring的底层注解,给容器导入一个组件
@Import(AutoConfigurationImportSelector.class)
//告诉springboot开启自动配置功能,这样自动配置才能生效
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
//返回不会被导入到spring容器中的类
Class<?>[] exclude() default {};
//返回不会被导入到Spring容器中的类名
String[] excludeName() default {};
}
spring中有很多以Enable开头的注解,其作用就是借助@Import来收集并注册特定场景相关的bean,并加载到IOC容器。
@AutoConfigurationPackage如下代码所示:
@Import(AutoConfigurationPackages.Registrar.class)//导入Registrar中注册的组件
public @interface AutoConfigurationPackage {
}
@Import就是将Register这个组件类导入到容器中,查看Register
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));
}
}
在register处打个断点。
再看register方法
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
//如果该bean已经注册,则将要注册包名称添加进去
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
}
else {
//如果尚未注册,则注册该bean,参数中提供的包名称会被设置到bean定义中去
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}
AutoConfigurationPackages.Registrar这个类此时就注册了一个bean,这个bean就是org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages,这个参数使用了 @AutoConfigurationPackage 这个注解的类所在的包路径,保存自动配置类以供之后使用。
接着分析@Import(AutoConfigurationImportSelector.class
将AutoConfigurationImportSelector类导入到spring容器中,这个类可以帮助springboot应用将所有符合条件的@Configuration配置都加载到当前springboot创建并使用的IOC容器中。
通过调用栈
确定自动配置实现逻辑的入口方法,看下DeferredImportSelectorGrouping 类的getImports方法处。
public Iterable<Entry> getImports() {
//集合中装了各种ImportSelector
Iterator var1 = this.deferredImports.iterator();
while(var1.hasNext()) {
ConfigurationClassParser.DeferredImportSelectorHolder deferredImport = (ConfigurationClassParser.DeferredImportSelectorHolder)var1.next();
//利用AutoConfigurationGroup的process方法来处理自动配置相关逻辑,决定导入哪些配置类(重点)
this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());
}
//经过上面的处理后,然后进行选择导入哪些配置类
return this.group.selectImports();
}
接着进入AutoConfigurationImportSelector 类中的AutoConfigurationGroup的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 autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
//封装了自动配置类的autoConfigurationEntry对象进入autoConfigurationEntries集合
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
//遍历刚获取的自动配置类,符合条件的自动配置类作为key,annotationMetadata放入集合
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
getAutoConfigurationEntry 这个方法主要是用来获取自动配置类有关。
//获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//得到spring.factories文件配置的所有自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
//移除重复的配置类
configurations = removeDuplicates(configurations);
//得到要排除的自动配置类,比如注解属性exclude的配置类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
//检查要排除的配置类,因为有些不是自动配置类,要抛出异常
checkExcludedClasses(configurations, exclusions);
//将要排除的配置类移除
configurations.removeAll(exclusions);
//会调用AutoConfigurationImportFilter的match方法判断是否符合@ConditionalOnBean或@ConditionalOnClass
configurations = filter(configurations, autoConfigurationMetadata);
//获取了符合条件的自动配置类后,会触发AutoConfigurationImportEvent事件
//目的告诉ConditionEvaluationReport条件评估报告器记录符合条件的自动配置类
//什么时候被触发----刷新容器时调用invokeBeanFactoryPostProcessor后置处理器时触发
fireAutoConfigurationImportEvents(configurations, exclusions);
//将符合条件的和要排除的自动配置类封装对象并返回
return new AutoConfigurationEntry(configurations, exclusions);
}
看下getCandidateConfigurations方法
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;
}
有个loadFactoryNames 方法,这个方法就是让SpringFactoryLoader 去加载一些组件的名字。进入方法内部
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
从代码中可以看出,这个方法会遍历整个ClassLoader所有jar包下的spring.factories文件,文件里保存着springboot默认提供的自动配置类。
META-INF/spring.factories部分内容如下
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,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
总结下AutoConfigurationEntry 主要做的事情:
1.从spring.factories配置文件中加载EnableAutoConfiguration 自动配置类
2.若 @EnableAutoConfiguration 等注解标有要 exclude 的自动配置类,那么再将这个自动配置类 排除掉;
3.排除掉要 exclude 的自动配置类后,然后再调用 filter 方法进行进一步的过滤,再次排除一些 不符合条件的自动配置类;
4.经过重重过滤后,此时再触发 AutoConfigurationImportEvent 事件,告诉 ConditionEvaluationReport 条件评估报告器对象来记录符合条件的自动配置类;
5.后再将符合条件的自动配置类返回。
然后看一下filter方法:
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
long startTime = System.nanoTime();
//自动配置类转出字符串数组
String[] candidates = StringUtils.toStringArray(configurations);
//表示是否跳过
boolean[] skip = new boolean[candidates.length];
boolean skipped = false;
//拿到OnBeanCondition,OnClassCondition和OnWebApplicationCondition 然后遍历去过滤spring.factories加载的大量配置类
for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
invokeAwareMethods(filter);
// 各种filter来判断每个candidate(这里实质要通过candidate(自动配置类)拿到其标注的
//@ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication里 面的注解值)是否匹配,
//注意candidates数组与match数组一一对应
boolean[] match = filter.match(candidates, autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
skip[i] = true;
candidates[i] = null;
skipped = true;
}
}
}
if (!skipped) {
return configurations;
}
List<String> result = new ArrayList<>(candidates.length);
for (int i = 0; i < candidates.length; i++) {
if (!skip[i]) {
result.add(candidates[i]);
}
}
if (logger.isTraceEnabled()) {
int numberFiltered = configurations.size() - result.size();
logger.trace("Filtered " + numberFiltered + " auto configuration class in "
+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
}
return new ArrayList<>(result);
}
filter方法主要做的事情是调用AutoConfigurationImportFilter 接口的match方法来判断每一个自动配置类上的条件注解是否满足条件。
关于条件注解的讲解
@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册 bean。 @ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。 @ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。 @ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。基于SpEL表达式 的条件判断。 @ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。 @ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。 @ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。 @ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。 @ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。 @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。 @ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。 @ConditionalOnResource:当类路径下有指定的资源时触发实例化。 @ConditionalOnJndi:在JNDI存在的条件下触发实例化。 @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首 选的Bean时触发实例化。
接下来有选择的导入自动配置类。
跟着断点走进this.group.selectImport方法,代码如下:
public Iterable<Entry> selectImports() {
if (this.autoConfigurationEntries.isEmpty()) {
return Collections.emptyList();
}
//得到所有要排除的自动配置类的set集合
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());
}
总结:
springboot自动配置主要做了从spring.factories配置文件中加载自动配置类;加载的自动配置类中排除掉@EnableAutoConfiguration注解的 exclude 属性指定的自动配置类; 然后再用 AutoConfigurationImportFilter 接口去过滤自动配置类是否符合其标注注解(若有标注的话) @ConditionalOnClass , @ConditionalOnBean 和 @ConditionalOnWebApplication 的条件,若都符合的话则返回匹配结果;然后触发 AutoConfigurationImportEvent 事件,告诉 ConditionEvaluationReport 条件评 估报告器对象来分别记录符合条件和 exclude 的自动配置类;后spring再将后筛选后的自动配置类导入IOC容器中。