SpringBoot核心流程简图
SpringBoot启动会自动加载相关依赖,配置相应的初始化参数,以最快捷、简单的形式对第三方软件进行集成,这便是springboot的自动配置功能。我们从整体上看一下springboot实现该运作机制涉及的核心部分
![](https://img-blog.csdnimg.cn/img_convert/6df0a7dbafb988daec801e9700fcc71a.png)
SpringBoot通过@EnableAutoConfiguration注解开启自动配置,加载spring.factories中注册的各种AutoConfiguration类,当某个AutoConfiguration类满足其注@Conditional指定的生效条件时,实例化该AutoConfiguration类中定义的Bean(组件等),并注入到Spring容器/SpringBoot容器里,就可以完成依赖框架的自动配置。
@EnableAutoConfiguration:该注解由组合注解@SpringBootApplication引入,完成自动配置开启,扫描各个jar包下的spring.factories文件,并加载文件中注册的AutoConfiguration类等。
spring.factories:配置文件,位于jar包的META-INF目录下,按照指定格式注册了自动配置的AutoConfiguration类。spring.factories也可以包含其他类型待注册的类。该配置文件不仅仅存在于SpringBoot项目中,也可以存在于自定义的自动配置(或Starter)项目中
AutoConfiguration类:自动配置类,代表了springBoot中一类以XXAutoConfiguration命名的自动配置类。其中定义了三方组件继承Spring所需初始化的Bean和条件。
@Conditional:条件注解及其衍生注解,在AutoConfiguration类上使用,当满足该条件注解时,才会实例化AutoConfiguration类。
Starters:三方组件的依赖和配置,SpringBoot已经预先配置的组件。
@SpringBootApplication源码及使用介绍
@SpringBootApplication注解是由多个注解组合而成
![](https://img-blog.csdnimg.cn/img_convert/a266fad7d031871b23e13da17d1b3f10.png)
@Target({ElementType.TYPE}) 用来表示注解作用范围,TYPE 表示作用范围为类或接口
![](https://img-blog.csdnimg.cn/img_convert/e6f9116770e37f19dbeaa29fb6e13436.png)
@Retention (RetentionPolicy.RUNTIME)
![](https://img-blog.csdnimg.cn/img_convert/8ca33ed95bf18cb923d92c3f01ff002d.png)
@Documented 表明这个注释是由 javadoc 记录的。
@Inherited 放在注解上,当父类加了 @SpringBootApplication 注解时,子类也会继承这个注解(对接口的实现类无效)。
@SpringBootConfiguration @ComponetScan @EnableAutoConfiguration
@SpringBootConfiguration注解下有@Configuration,这里就是我们平时自定义的Configuration
@EanbleAutoConfiguration注解下有@AutoConfigurationPackage、@Import注解,这里是自动配置的Configuration
@ComponentScan 执行一些包路径扫描
![](https://img-blog.csdnimg.cn/img_convert/6f4be8d33859ba7c29a687078f9c1280.png)
源码方法介绍及使用:
exclude:根据类(Class)排除指定的自动配置,该成员属性覆盖了@SpringBootApplication中组合的@EnableAutoConfiguration中定义的exclude成员属性。
excludeName:根据类名排除指定的自动配置,覆盖了@EnableAutoConfiguration中的excludeName的成员属性
scanBasePackages:指定扫描的基础package,用于激活@Component等注解的初始化
scanBasePackageClasses:指定扫描的类,用于组件的初始化
proxyBeanMethods:指定是否代理@Bean方法以强制执行bean的生命周期行为
PS:exclude、excludeName覆盖了@EnableAutoConfiguration中的成员属性释义:表示@SpringBootApplication可以继承@EnableAutoConfiguration中的exclude、excludeName属性
@EnableAutoConfiguration
@EnableConfiguration是开启自动配置的注解,在创建的SpringBoot项目中并不能直接看到这个注解,它是由组合注解@SpringBootApplication引入的。在SpringBoot入口类(单元测试除外)中,唯一的注解就是@SpringBootApplication。它是SpringBoot项目的核心注解,用于开启自动配置,准确说是通过该注解内组合的@EnableConfiguration开启了自动配置。
在未使用springBoot的情况下,Bean的生命周期由Spring来管理,然而Spring无法自动配置@Configuration注解的类。而SpringBoot的核心功能之一,就是根据约定自动管理该注解标注的类。用来实现该功能的组件之一便是@EnableAutoConfiguration注解。@EnableAutoConfiguration位于SpringBootAutoConfiguration包内,当使用@SpringBootApplication注解时,@EnableAutoConfiguration注解就会自动生效。@EnableAutoConfiguration的主要功能就是启动spring应用程序上下文时进行自动配置
@EnableAutoConfiguration注解提供了一个常量和两个成员参数的定义。
·ENABLED_OVERRIDE_PROPERTY:用来覆盖开启/关闭自动配置的功能。
·exclude:根据类(Class)排除指定的自动配置。
·excludeName:根据类名排除指定的自动配置。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
// 请详细关注AutoConfigurationImportSelector,这里是导入我们的自动配置类
public @interface EnableAutoConfiguration {
/**
* Environment property that can be used to override when auto-configuration is
* enabled.
* 标志是否打开自动配置项
*/
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
* 根据类名进行排除
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
* 根据名称进行排除
*/
String[] excludeName() default {};
}
AutoConfigurationImportSelector
@EnableAutoConfiguration的关键功能是通过@Import注解导入的ImportSelector来完成的。从源码得知@Import(AutoConfigurationImportSelector.class)是@EnableAutoConfiguration注解的组成部分,也是自动配置功能的核心实现者。
![](https://img-blog.csdnimg.cn/img_convert/fe5f7532dca5ab7c9d526a536f095c6c.png)
![](https://img-blog.csdnimg.cn/img_convert/b63e6322ded8c28b180561377f518ce5.png)
![](https://img-blog.csdnimg.cn/img_convert/d063e6da06cc919c201098f645107645.png)
从上图我们可以看出,AutoConfigurationImportSelector是ImportSelector接口的子类。这里我们可以通过@Import引入@Configuration注解的类,也可以导入实现了ImportSelector或ImportBeanDefinitionRegistrar的类,还可以通过@Import导入普通的POJO。
@Import的许多功能都需要借助接口ImportSelector来实现,ImportSelector决定可引入哪些@Configuration。
AutoConfigurationImportSelector作为ImportSelector的子类,当AutoConfigurationImportSelector被@Import注解引入后,它的selectImports方法就会被调用,并执行其实现的自动装配逻辑。selectImports方法几乎涵盖了组件自动装配的所有处理逻辑。
![](https://img-blog.csdnimg.cn/img_convert/abc60076ecab37ff1e46338587989bc3.png)
对于selectImports来说,有两种形式。一种是实现了DeferredImportSelector,一种是实现了ImportSelector。DeferredImportSelector来说,不会调用selectImports方法进行配置加载,ImportSelector形式会通过selectImports方法进行配置加载;
DeferredImportSelector这种是先进行我们的@Configuration配置的加载,然后进行其他(AutoConfig相关类)的加载;ImportSelector先加载AutoConfig相关类,然后加载@Configuration类。
// 部分代码已省略。。。
// 这里是跳转到上图selectImports方法的入口
// 当实现了DeferredImportSelector也就走不到selectImports
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
// 只有实现ImportSelector才可以走到selectImports
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
getAutoConfigurationEntry
上面我们分析了selectImports的入口,在selectImports方法中,有着我们至关重要的getAutoConfigurationEntry方法。它是获取我们所有的自动配置
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 自动配置是否已启用
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取注解的属性 (exclude 和 excludeName)
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取自动配置类,从spring.factories中获取
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去除重复的配置类,List转换成LinkedHashSet
configurations = removeDuplicates(configurations);
// 通过我们的注解属性,获取exclude的类(排除的配置类)
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
//检测 exclusions里面的类是否是正规的类
checkExcludedClasses(configurations, exclusions);
// 从集合中删除所有要排除的类
configurations.removeAll(exclusions);
// 筛选Configurations,剔除了我们不需要加载的配置类
configurations = getConfigurationClassFilter().filter(configurations);
// 事件发布
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
getAttributes
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
// 获取EnableAutoConfiguration.class 全类名
String name = getAnnotationClass().getName();
// 拿到指定类型注解的元数据信息
// getAnnotationAttributes获取指定注解的所有属性-值(k-v)
// 当第二个参数为true表示Class用它的字符串的全类名来表示,这样可以避免Class被提前加载
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName()
+ " annotated with " + ClassUtils.getShortName(name) + "?");
return attributes;
}
getCandidateConfigurations
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// getSpringFactoriesLoaderFactoryClass 获取EnableAutoConfiguration.class
// getBeanClassLoader 获取当前类的类加载器
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. 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) {
// 类加载器赋值给classLoaderToUse
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 获取EnableAutoConfiguration类的全类名
String factoryTypeName = factoryType.getName();
// 用classLoaderToUse类加载器加载我们的配置类
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
getOrDefault返回的是一个map类型,这里值得注意的是为什么需要返回map类型?spring.factories文件中所有的配置类都是以键值对形势展开,而我们需要读取spring.factories文件,使用Map<String, List<String>>也就正好对应了spring.factories的文件结构
![](https://img-blog.csdnimg.cn/img_convert/ba6b3f897c6bde43ab83bc1a906fc8db.png)
上面我们知道了spring.factories的文件类型,那么就让我们看一下loadSpringFactories是如何进行加载和封装的
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
// 获取当前classLoad加载过的result
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
// public tatic final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// 这里就是加载的spring.factories的入口
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 封装成UrlResource
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
// 一直到这里才会封装成一个 Map<String, List<String>>
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
// 然后在这里添加进了缓存中
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
在上面我们知道了spring.factories是怎么进行加载的。那么META-INF/spring.factories这个东西什么时候才需要使用呢?我们自己的项目中并没有这个文件的添加。什么情况下我们才需要这部分配置写到自己的项目的spring.factories里面呢?
首先确定spring.factories文件,它能够让我们springboot加载我们的一些配置信息。在这里我们除了使用spring.factories这种形式,还能够使用@Configuration等形式进行配置的加载啊?
这里需要思考的是,我们在pom文件中引入了一个第三方的jar包,并且我们需要将该jar包里面的某一个或者多个配置类 进行我们当前项目的springboot的配置注入,我们应该怎么办?用spring.factories。这个时候我们就用不到@Configuration
getExclusions
通过我们的注解属性,获取exclude的类(排除的配置类)
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
Set<String> excluded = new LinkedHashSet<>();
excluded.addAll(asList(attributes, "exclude"));
excluded.addAll(asList(attributes, "excludeName"));
// 这里需要注意的是spring为我们考虑了如果在配置文件中加入了排除项也会被考虑进来
excluded.addAll(getExcludeAutoConfigurationsProperty());
return excluded;
}
checkExcludedClasses
private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
// 获取所有的排除项的集合
List<String> invalidExcludes = new ArrayList<>(exclusions.size());
// 遍历
for (String exclusion : exclusions) {
// 判断是否被JVM加载过
// 判断是否在原始的configurations中
// 这里是一个安全的校验
if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
invalidExcludes.add(exclusion);
}
}
// 当上面出现了安全风险,这里就会抛出异常
if (!invalidExcludes.isEmpty()) {
handleInvalidExcludes(invalidExcludes);
}
}
getConfigurationClassFilter().filter(configurations);
getConfigurationClassFilter获取所有的过滤器,这些过滤器是在spring-boot-antoconfigure的jar包中的spring.factories文件中获取的
为什么叫做OnXXXCondition?On就是表示你引入了么?Condition是否符合某个条件
![](https://img-blog.csdnimg.cn/img_convert/af7fcc17afbfd3f2ef46a52590a1d2e6.png)
上面我们通过getConfigurationClassFilter方法获取了三个自动配置的过滤器,下面就让我们看一下filter方法
List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
boolean skipped = false;
for (AutoConfigurationImportFilter filter : this.filters) {
// match 判断是否为ture,如果为ture表示需要加载,flase表示不加载
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
candidates[i] = null;
skipped = true;
}
}
}
if (!skipped) {
return configurations;
}
List<String> result = new ArrayList<>(candidates.length);
for (String candidate : candidates) {
if (candidate != null) {
result.add(candidate);
}
}
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 result;
}
上面我们看到了filter中的match方法,在springboot中专门为此写了一个子类实现FilteringSpringBootCondition
@Override
public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
// 这里把spring自带的配置进行了一个判断,需要加载的设置为ture,不需要加载的设置为flase
// 这里也是我们的关键方法,下面只是针对此结果集进行的一些判断处理
// getOutcomes方法,是一个抽象方法,有三个实现类,OnXXXCondition正好对象上面的三个过滤器,并且在其中进行一个过滤
ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
boolean[] match = new boolean[outcomes.length];
for (int i = 0; i < outcomes.length; i++) {
match[i] = (outcomes[i] == null || outcomes[i].isMatch());
if (!match[i] && outcomes[i] != null) {
logOutcome(autoConfigurationClasses[i], outcomes[i]);
if (report != null) {
report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
}
}
}
return match;
}
other
到这里我们的springboot核心运行流程已经阅读完毕,回顾最开始的核心流程图。我们阅读完了@SpringBootApplication、@EnableAutoConfiguration以及@Import注解,那么还剩下@EnableAutoConfiguration下的@AutoConfigurationPackage还没有阅读。
不过之前已经阅读过spring源码 ,这里的功能无外乎就是把我们的AutoConfiguration的bean信息注册到beanFactory中
![](https://img-blog.csdnimg.cn/img_convert/72156eee4625bfd96172b0f3966cb877.png)
注册beanDefinition到beanFactory中
![](https://img-blog.csdnimg.cn/img_convert/5bda09362be89e2f360f7e751b3e7726.png)
调用registerBeanDefinition方法
![](https://img-blog.csdnimg.cn/img_convert/72b6283c603cd3c72c280e5e4dd5f6cc.png)
进入DefaultListableBeanFactory,这里就是我们熟悉的spring源码环节
![](https://img-blog.csdnimg.cn/img_convert/5630f8e32c981956e243a3c76794a23b.png)
回顾以上阅读环节。@EnableAutoConfiguration注解其实就是把我们的AutoConfiguration这些东西进行一个bean的注册,beanDefinition注册完成以后,通过AutoConfigurationImportSelector进行一个筛选加载
到此,我们的springboot的核心运行流程已经全部阅读完毕