SpringBoot启动原理详解
SpringBoot和Spring相比,有着不少优势,比如自动配置,jar直接运行等等。那么SpringBoot到底是怎么启动的呢?
下面是SpringBoot启动的入口:
@SpringBootApplication
public class HelloApplication { public static void main(String[] args) {
SpringApplication.run(HelloApplication.class, args);
}
}
一、先看一下@SpringBoot注解:
@Target({ElementType.TYPE}) //定义其使用时机
@Retention(RetentionPolicy.RUNTIME) //编译程序将Annotation储存于class档中,可由VM使用反射机制的代码所读取和使用。
@Documented //这个注解应该被 javadoc工具记录
@Inherited //被注解的类会自动继承. 更具体地说,如果定义注解时使用了 @Inherited 标记,然后用定义的注解来标注另一个父类, 父类又有一个子类(subclass),则父类的所有属性将被继承到它的子类中.
@SpringBootConfiguration //@SpringBootConfiguration就相当于@Configuration。JavaConfig配置形式
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)} //自动扫描并加载符合条件的组件。以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。
注:所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。
)
public @interface SpringBootApplication {
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {}; @AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {}; @AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {}; @AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
}
所以,实际上SpringBootApplication注解相当于三个注解的组合,@SpringBootConfiguration,@ComponentScan和@EnableAutoConfiguration。
@SpringBootConfiguration和@ComponentScan,很容易知道它的意思,一个是JavaConfig配置,一个是扫描包。关键在于@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 {};
}
Springboot应用启动过程中使用ConfigurationClassParser分析配置类时,如果发现注解中存在@Import(ImportSelector)的情况,就会创建一个相应的ImportSelector对象, 并调用其方法 public String[] selectImports(AnnotationMetadata annotationMetadata), 这里 EnableAutoConfigurationImportSelector的导入@Import(EnableAutoConfigurationImportSelector.class) 就属于这种情况,所以ConfigurationClassParser会实例化一个 EnableAutoConfigurationImportSelector 并调用它的 selectImports() 方法。
AutoConfigurationImportSelector implements DeferredImportSelector extends ImportSelector。
下面是AutoConfigurationImportSelector的执行过程:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
private static final String[] NO_IMPORTS = new String[];
private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
private ConfigurableListableBeanFactory beanFactory;
private Environment environment;
private ClassLoader beanClassLoader;
private ResourceLoader resourceLoader;
public AutoConfigurationImportSelector() {
} public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
// 从配置文件中加载 AutoConfigurationMetadata
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 获取所有候选配置类EnableAutoConfiguration
// 使用了内部工具使用SpringFactoriesLoader,查找classpath上所有jar包中的
// META-INF\spring.factories,找出其中key为
// org.springframework.boot.autoconfigure.EnableAutoConfiguration
// 的属性定义的工厂类名称。
// 虽然参数有annotationMetadata,attributes,但在 AutoConfigurationImportSelector 的
// 实现 getCandidateConfigurations()中,这两个参数并未使用
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//去重
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
// 应用 exclusion 属性
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 应用过滤器AutoConfigurationImportFilter,
// 对于 spring boot autoconfigure,定义了一个需要被应用的过滤器 :
// org.springframework.boot.autoconfigure.condition.OnClassCondition,
// 此过滤器检查候选配置类上的注解@ConditionalOnClass,如果要求的类在classpath
// 中不存在,则这个候选配置类会被排除掉
configurations = this.filter(configurations, autoConfigurationMetadata);
// 现在已经找到所有需要被应用的候选配置类
// 广播事件 AutoConfigurationImportEvent
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
} protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
String name = this.getAnnotationClass().getName();
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
Assert.notNull(attributes, () -> {
return "No auto-configuration attributes found. Is " + metadata.getClassName() + " annotated with " + ClassUtils.getShortName(name) + "?";
});
return attributes;
} protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
} public abstract class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap(); public SpringFactoriesLoader() {
}
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
} private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
// 记录候选配置类是否需要被排除,skip为true表示需要被排除,全部初始化为false,不需要被排除
boolean[] skip = new boolean[candidates.length];
// 记录候选配置类中是否有任何一个候选配置类被忽略,初始化为false
boolean skipped = false;
Iterator var8 = this.getAutoConfigurationImportFilters().iterator();
// 获取AutoConfigurationImportFilter并逐个应用过滤
while(var8.hasNext()) {
AutoConfigurationImportFilter filter = (AutoConfigurationImportFilter)var8.next();
// 对过滤器注入其需要Aware的信息
this.invokeAwareMethods(filter);
// 使用此过滤器检查候选配置类跟autoConfigurationMetadata的匹配情况
boolean[] match = filter.match(candidates, autoConfigurationMetadata); for(int i = ; i < match.length; ++i) {
if (!match[i]) {
// 如果有某个候选配置类不符合当前过滤器,将其标记为需要被排除,
// 并且将 skipped设置为true,表示发现了某个候选配置类需要被排除
skip[i] = true;
skipped = true;
}
}
} if (!skipped) {
// 如果所有的候选配置类都不需要被排除,则直接返回外部参数提供的候选配置类集合
return configurations;
} else {
// 逻辑走到这里因为skipped为true,表明上面的的过滤器应用逻辑中发现了某些候选配置类
// 需要被排除,这里排除那些需要被排除的候选配置类,将那些不需要被排除的候选配置类组成
// 一个新的集合返回给调用者
List<String> result = new ArrayList(candidates.length); int numberFiltered;
for(numberFiltered = ; numberFiltered < candidates.length; ++numberFiltered) {
if (!skip[numberFiltered]) {
result.add(candidates[numberFiltered]);
}
} if (logger.isTraceEnabled()) {
numberFiltered = configurations.size() - result.size();
logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
} return new ArrayList(result);
}
} /**
* 使用内部工具 SpringFactoriesLoader,查找classpath上所有jar包中的
* META-INF\spring.factories,找出其中key为
* org.springframework.boot.autoconfigure.AutoConfigurationImportFilter
* 的属性定义的过滤器类并实例化。
* AutoConfigurationImportFilter过滤器可以被注册到 spring.factories用于对自动配置类
* 做一些限制,在这些自动配置类的字节码被读取之前做快速排除处理。
* spring boot autoconfigure 缺省注册了一个 AutoConfigurationImportFilter :
* org.springframework.boot.autoconfigure.condition.OnClassCondition
**/
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}
二、下面看一下SpringBoot启动时run方法执行过程。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
this.configureHeadlessProperty();
//从类路径下META-INF/spring.factories获取
SpringApplicationRunListeners listeners = getRunListeners(args);
//回调所有的获取SpringApplicationRunListener.starting()方法
listeners.starting();
try {
//封装命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//创建环境完成后回调
SpringApplicationRunListener.environmentPrepared();表示环境准备完成
this.configureIgnoreBeanInfo(environment);
//打印Banner图
Banner printedBanner = printBanner(environment);
//创建ApplicationContext,决定创建web的ioc还是普通的ioc
context = createApplicationContext();
//异常分析报告
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//准备上下文环境,将environment保存到ioc中
//applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法
//listeners.contextPrepared(context)
//prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded()
this.prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//刷新容器,ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat)
//扫描,创建,加载所有组件的地方,(配置类,组件,自动配置)
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//所有的SpringApplicationRunListener回调started方法
listeners.started(context);
//从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调,
//ApplicationRunner先回调,CommandLineRunner再回调
this.callRunners(context, applicationArguments);
}
catch (Throwable ex) {
this.handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
//所有的SpringApplicationRunListener回调running方法
listeners.running(context);
}
catch (Throwable ex) {
this.handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
//整个SpringBoot应用启动完成以后返回启动的ioc容器
return context;
}
SpringBoot启动原理详解的更多相关文章
- Tomcat启动过程原理详解 -- 非常的报错:涉及了2个web.xml等文件的加载流程
Tomcat启动过程原理详解 发表于: Tomcat, Web Server, 旧文存档 | 作者: 谋万世全局者 标签: Tomcat,原理,启动过程,详解 基于Java的Web 应用程序是 ser ...
- SpringBoot启动原理及相关流程
一.springboot启动原理及相关流程概览 springboot是基于spring的新型的轻量级框架,最厉害的地方当属自动配置.那我们就可以根据启动流程和相关原理来看看,如何实现传奇的自动配置 二 ...
- SpringBoot启动原理
SpringBoot启动原理 我们开发任何一个Spring Boot项目,都会用到如下的启动类: @SpringBootApplication public class Application { p ...
- Influxdb原理详解
本文属于<InfluxDB系列教程>文章系列,该系列共包括以下 15 部分: InfluxDB学习之InfluxDB的安装和简介 InfluxDB学习之InfluxDB的基本概念 Infl ...
- TOMCAT原理详解及请求过程(转载)
转自https://www.cnblogs.com/hggen/p/6264475.html TOMCAT原理详解及请求过程 Tomcat: Tomcat是一个JSP/Servlet容器.其作为Ser ...
- Namenode HA原理详解(脑裂)
转自:http://blog.csdn.net/tantexian/article/details/40109331 Namenode HA原理详解 社区hadoop2.2.0 release版本开始 ...
- Storm概念、原理详解及其应用(一)BaseStorm
本文借鉴官文,添加了一些解释和看法,其中有些理解,写的比较粗糙,有问题的地方希望大家指出.写这篇文章,是想把一些官文和资料中基础.重点拿出来,能总结出便于大家理解的话语.与大多数“wordcount” ...
- Java网络编程和NIO详解6:Linux epoll实现原理详解
Java网络编程和NIO详解6:Linux epoll实现原理详解 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NIO h ...
- NFS原理详解
NFS原理详解 摘自:http://atong.blog.51cto.com/2393905/1343950 2013-12-23 12:17:31 标签:linux NFS nfs原理详解 nfs搭 ...
随机推荐
- 【转】Linux 系统如何处理名称解析
原文写的很好:https://blog.arstercz.com/linux-%E7%B3%BB%E7%BB%9F%E5%A6%82%E4%BD%95%E5%A4%84%E7%90%86%E5%90% ...
- Loadrunner11压测过程问题总结
1.-27727: 下载资源时步骤下载超时 (120 seconds) 已过期 由于压力大了,下载资源所用时间就长了,可以设置加大超时时间: 运行时设置--Internet 协议--首选项--高级-- ...
- CMake版本低,需要更高版本.
https://blog.csdn.net/qq_34935373/article/details/90266958 使用cmake命令安装Opencv软件时,报如下错误: CMake Error a ...
- cmd find命令用法
Windows命令行(cmd)下快速查找文件(类似Linux下find命令) 2015年04月11日 10:50:43 开水 阅读数 62240 标签: 命令行cmdwindows 更多 个人分类: ...
- leetcode 968. Binary Tree Cameras
968. Binary Tree Cameras 思路:如果子节点只能覆盖到父节点.当前节点,但是父节点可以覆盖到他的父节点.子节点.当前节点,所以从叶子节点往上考虑 0代表子节点没有被覆盖 1代表子 ...
- android 8.1 wifi提示"已连接 但无法访问互联网"的解决办法
主要是GFW的问题 adb shell以下命令解决 settings put settings put settings put settings put global captive_portal_ ...
- SQL Server 2008 R2 安装 下载
[参考]https://www.aiweibk.com/6697.html winrm 服务未启动,需要先配置.以管理员身份启动 cmd,执行 winrm quickconfig 命令. 微信截图_2 ...
- 由crt和key文件生成keystore文件
该图转自知乎 海棠依旧 1.先生成p12文件,生成的时候需要指定密码 openssl pkcs12 -export -in your_crt.crt -inkey your_key.key -out ...
- EasyDSS高性能流媒体服务器开发RTMP直播同步输出HLS(m3u8)录像功能实现时移回放的方案
EasyDSS商用流媒体服务器解决方案是由EasyDarwin开源团队原班人马开发的一套集流媒体点播.转码与管理.直播.录像.检索.时移回看于一体的一套完整的商用流媒体服务器解决方案,支持RTMP推流 ...
- php对数组遍历的两种方式示例
在对 php 数组遍历时,一般经常使用 foreach 来遍历,很少用 while 来遍历,在下面的代码中作一个对比. <?php $content = ["ID" => ...