什么是自动装配:
根据我们添加的jar包依赖,自动的将一些bean注册到ioc容器中。当我们需要的时候,直接就可以使用这些bean,而不需要事先手动添加。也就是我们常说的开箱即用。
问题:
- Spring Boot是怎么实现自动装配的
- 哪些资源会被自动装配
Spring Boot启动入口,被@SpringBootApplication标注的类,就是Spring Boot的启动类,通过运行该类,可以启动Spring Boot.
@SpringBootApplication
public class SpringBootMytestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootMytestApplication.class, args);
}
}
@SpringBootApplication
@SpringBootApplication源码

@SpringBootApplication源码核心逻辑分析
- @SpringBootConfiguration:标注的类将作为配置类
- @EnableAutoConfiguration:启动自动装配
- @ComponentScan(excludeFilters = {...}):扫描配置路径的包,默认路径为标注该注解的类所在路径
- Class<?>[] exclude() default {}:根据class排除类,使其不加入spring容器
- Class<?>[] exclude() default {}:根据classname排除类,使其不加入spring容器
- String[] scanBasePackages() default {}:扫描配置的包路径
- Class<?>[] scanBasePackageClasses() default {}:扫描特定的包路径
从源码可以知道@SpringBootApplication其实是一个组合注解,接下来我们看下里面的三个核心注解:@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan
@SpringBootConfiguration
@SpringBootConfiguration源码

从源码可以看出来@SpringBootConfiguration其实是@Configuration注解的包装,用来标志一个类是配置类。
@EnableAutoConfiguration
@EnableAutoConfiguration源码

@EnableAutoConfiguration源码分析
- @AutoConfigurationPackage:自动配置包
- @Import(...):Spring的底层注解@Import,将AutoConfigurationImportSelector.class导入到容器中
- Class<?>[] exclude() default {}: 返回不会被导入到 Spring 容器中的类
- String[] excludeName() default {}:返回不会被导入到 Spring 容器中的类名
@EnableAutoConfiguration是一个组合注解,其中的核心注解是@AutoConfigurationPackage,@Import
@AutoConfigurationPackage
@AutoConfigurationPackage源码

@AutoConfigurationPackage也是一个组合注解,核心注解@Import(AutoConfigurationPackages.Registrar.class)的作用是将一个组件加入到容器中,这里就是将Registrar导入到容器中。
我们来看一下Registrar类中的registerBeanDefinitions方法,它的作用是将注解标注的元信息传入,并获取到相应的包名。
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
再看下register方法
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);
}
}

可以看到register方法会注册一个BeanDefinition,也就是class org.springframework.boot.autoconfigure.AutoConfigurationPackages$BasePackages到容器中,这个BeanDefinition中保存了@AutoConfigurationPackage注解标注类所在到包路径。
@Import(AutoConfigurationImportSelector.class)
@Import(AutoConfigurationImportSelector.class)注解会将AutoConfigurationImportSelector.class导入到容器中,AutoConfigurationImportSelector 可以帮助Spring Boot 将所有符合条件的 @Configuration配置都加载到当前Spring Boot 创建并使用的IOC容器中。
AutoConfigurationImportSelector的类图,可以看到它实现了DeferredImportSelector,Ordered,以及各种Aware接口,这代表着AutoConfigurationImportSelector具有优先级,可以在Spring加载过程中获取到Environment,BeanFactory等资源, 并且会执行DeferredImportSelectorGrouping 类的 getImports方法。

来看下org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGrouping#getImports
public Iterable<Group.Entry> getImports() {
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
return this.group.selectImports();
}
}
遍历DeferredImportSelectorHolder对象集合deferredImports,deferredImports集合存放了各种ImportSelector,当然这里装的是AutoConfigurationImportSelector,然后调用this.group.process(...),最后再返回 this.group.selectImports()。
我们来看下org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process
@Override
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()));
/**
* 调用getAutoConfigurationEntry方法得到自动配置类放入autoConfigurationEntry对象中
*/
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
/**
* 将封装了自动配置类的autoConfigurationEntry对象装进 autoConfigurationEntries集合
*/
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
/**
* 将符合条件的自动配置类作为key,annotationMetadata作为值放进entries集合
*/
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
上面的代码中,我们来看下org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry,这个方法主要用来在自动装配中加载需要注入到容器中的类。
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
/**
* 获取是否有配置spring.boot.enableautoconfiguration属性,默认返回true.
*/
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
/**
* 获得@Congiguration标注的Configuration类即被审视introspectedClass的注解数据
* 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
* 将会获取到exclude = FreeMarkerAutoConfiguration.class和excludeName=""的注解数据.
*/
AnnotationAttributes attributes = getAttributes(annotationMetadata);
/**
* 【1】得到spring.factories文件配置的所有自动配置类
*/
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
/**
* 【2】因为从spring.factories文件获取的自动配置类太多,如果有些不必要的自动配置类都加载进内存,会造成内存浪费,因此这里需要进行过滤
* 注意这里会调用AutoConfigurationImportFilter的match方法来判断是否符合
* @ConditionalOnBean,@ConditionalOnClass或@ConditionalOnWebApplication
*/
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
/**
* 【3】将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回
*/
return new AutoConfigurationEntry(configurations, exclusions);
}
进入到【1】getCandidateConfigurations(annotationMetadata, attributes);方法中,一直来到org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

获取工厂类型名称org.springframework.boot.autoconfigure.EnableAutoConfiguration ,然后调用org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories
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);
}
}
}
Spring Boot在这里加载环境中所有的META-INF/spring.factories中的信息,循环Enumeration类对象,根据相应的节点信息生成Properties对象,通过键org.springframework.boot.autoconfigure.EnableAutoConfiguration 获取值,在将值切割为一个个字符串转化为Array,添加到result集合中.


Spring Boot将result返回,并在org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry中做进一步处理
- removeDuplicates(configurations): 利用LinkedHashSet移除重复的配置类,避免重复配置
- getExclusions(annotationMetadata, attributes):获得要排除的类(配置在注解属性exclude中的类)
- checkExcludedClasses(configurations, exclusions): 检查要被排除的配置类
- configurations.removeAll(exclusions):将需要排除的类移除
j接着我们进入【2】中看看
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;
/**
* getAutoConfigurationImportFilters方法:拿到OnBeanCondition,
* OnClassCondition和OnWebApplicationCondition然后遍历这三个条件类去过滤从
* spring.factories加载的大量配置类
*/
for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
invokeAwareMethods(filter);
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方法来判断每一个自动配置类上的条件注解(若有的话) @ConditionalOnBean或 @ConditionalOnWebApplication是否满足条件,若满足则返回true,若不满足则返回false.可以看到执行完filter方法之后result集合的长度只有23了。
补充:@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时触发实例化。
现在来看org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry【3】,可以知道它其实就是将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回
AutoConfigurationEntry(Collection<String> configurations, Collection<String> exclusions) {
this.configurations = new ArrayList<>(configurations);
this.exclusions = new HashSet<>(exclusions);
}
然后我们再来到org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports,在这里从autoConfigurationEntries对象中取出应该注入和排除的配置类,再做一次过滤,过滤完成之后对实现了@Order注解的类进行排序,最后返回需要注入到容器的配置类。
@Override
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集合
*/
Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
.collect(Collectors.toCollection(LinkedHashSet::new));
/**
* 移除掉要排除的自动配置类
*/
processedConfigurations.removeAll(allExclusions);
/**
* 对标注有@Order注解的自动配置类进行排序,并返回.
*/
return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
.collect(Collectors.toList());
}

总结:
- 从spring.factories配置文件中通过key:org.springframework.boot.autoconfigure.EnableAutoConfiguration加载自动配置类
- 加载的自动配置类中排除掉EnableAutoConfiguration注解的exclude属性指定的自动配置类
- 再通过AutoConfigurationImportFilter接口根据条件过滤标注有条件注解的配置类
- 最后将需要注入的配置类交给spring,通过spring完成注入