SpringBoot启动流程是怎样的
前言
SpringBoot一开始最让我印象深刻的就是通过一个启动类就能启动应用。在SpringBoot以前,启动应用虽然也不麻烦,但是还是有点繁琐,要打包成war包,又要配置tomcat,tomcat又有一个server.xml文件去配置。
然而SpringBoot则内置了tomcat,通过启动类启动,配置也集中在一个application.yml中,简直不要太舒服。
一、启动类
首先我们看最常见的启动类写法。
@SpringBootApplication
public class SpringmvcApplication {public static void main(String[] args) {SpringApplication.run(SpringmvcApplication.class, args);}
}
把启动类分解一下,实际上就是两部分:
- @SpringBootApplication注解
- 一个main()方法,里面调用SpringApplication.run()方法。
二、@SpringBootApplication
首先看@SpringBootApplication注解的源码。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}
很明显,@SpringBootApplication注解由三个注解组合而成,分别是:
- @ComponentScan
- @EnableAutoConfiguration
- @SpringBootConfiguration
@ComponentScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {}
这个注解的作用是告诉Spring扫描哪个包下面类,加载符合条件的组件(比如贴有@Component和@Repository等的类)或者bean的定义。
所以有一个basePackages的属性,如果默认不写,则从声明@ComponentScan所在类的package进行扫描。
所以启动类最好定义在Root package下,因为一般我们在使用@SpringBootApplication时,都不指定basePackages的。
@EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
这是一个复合注解,看起来很多注解,实际上关键在@Import注解,它会加载AutoConfigurationImportSelector类,然后就会触发这个类的selectImports()方法。根据返回的String数组(配置类的Class的名称)加载配置类。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {//返回的String[]数组,是配置类Class的类名@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);//返回配置类的类名return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}
}
我们一直点下去,就可以找到最后的幕后英雄,就是SpringFactoriesLoader类,通过loadSpringFactories()方法加载META-INF/spring.factories中的配置类。
这里使用了spring.factories文件的方式加载配置类,提供了很好的扩展性。
所以@EnableAutoConfiguration注解的作用其实就是开启自动配置,自动配置主要则依靠这种加载方式来实现。
@SpringBootConfiguration
@SpringBootConfiguration继承自@Configuration,二者功能也一致,标注当前类是配置类, 并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中,并且实例名就是方法名。
小结
我们在这里画张图把@SpringBootApplication注解包含的三个注解分别解释一下。
SpringApplication类
接下来讲main方法里执行的这句代码,这是SpringApplication类的静态方法run()。
//启动类的main方法
public static void main(String[] args) {SpringApplication.run(SpringmvcApplication.class, args);
}//启动类调的run方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {//调的是下面的,参数是数组的run方法return run(new Class<?>[] { primarySource }, args);
}//和上面的方法区别在于第一个参数是一个数组
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {//实际上new一个SpringApplication实例,调的是一个实例方法run()return new SpringApplication(primarySources).run(args);
}
通过上面的源码,发现实际上最后调的并不是静态方法,而是实例方法,需要new一个SpringApplication实例,这个构造器还带有一个primarySources的参数。所以我们直接定位到构造器。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {this.resourceLoader = resourceLoader;//断言primarySources不能为null,如果为null,抛出异常提示Assert.notNull(primarySources, "PrimarySources must not be null");//启动类传入的Classthis.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));//判断当前项目类型,有三种:NONE、SERVLET、REACTIVEthis.webApplicationType = WebApplicationType.deduceFromClasspath();//设置ApplicationContextInitializersetInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));//设置监听器setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//判断主类,初始化入口类this.mainApplicationClass = deduceMainApplicationClass();
}//判断主类
private Class<?> deduceMainApplicationClass() {try {StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();for (StackTraceElement stackTraceElement : stackTrace) {if ("main".equals(stackTraceElement.getMethodName())) {return Class.forName(stackTraceElement.getClassName());}}}catch (ClassNotFoundException ex) {// Swallow and continue}return null;
}
得到SpringApplication实例后,接下来就调用实例方法run()。继续看。
public ConfigurableApplicationContext run(String... args) {//创建计时器StopWatch stopWatch = new StopWatch();//开始计时stopWatch.start();//定义上下文对象ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();//Headless模式设置configureHeadlessProperty();//加载SpringApplicationRunListeners监听器SpringApplicationRunListeners listeners = getRunListeners(args);//发送ApplicationStartingEvent事件listeners.starting();try {//封装ApplicationArguments对象ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);//配置环境模块ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);//根据环境信息配置要忽略的bean信息configureIgnoreBeanInfo(environment);//打印Banner标志Banner printedBanner = printBanner(environment);//创建ApplicationContext应用上下文context = createApplicationContext();//加载SpringBootExceptionReporterexceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);//ApplicationContext基本属性配置prepareContext(context, environment, listeners, applicationArguments, printedBanner);//刷新上下文refreshContext(context);//刷新后的操作,由子类去扩展afterRefresh(context, applicationArguments);//计时结束stopWatch.stop();//打印日志if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}//发送ApplicationStartedEvent事件,标志spring容器已经刷新,此时所有的bean实例都已经加载完毕listeners.started(context);//查找容器中注册有CommandLineRunner或者ApplicationRunner的bean,遍历并执行run方法callRunners(context, applicationArguments);}catch (Throwable ex) {//发送ApplicationFailedEvent事件,标志SpringBoot启动失败handleRunFailure(context, ex, exceptionReporters, listeners);throw new IllegalStateException(ex);}try {//发送ApplicationReadyEvent事件,标志SpringApplication已经正在运行,即已经成功启动,可以接收服务请求。listeners.running(context);}catch (Throwable ex) {//报告异常,但是不发送任何事件handleRunFailure(context, ex, exceptionReporters, null);throw new IllegalStateException(ex);}return context;
}
总结
表面启动类看起来就一个@SpringBootApplication注解,一个run()方法。其实是经过高度封装后的结果。我们可以从这个分析中学到很多东西。比如使用了spring.factories文件来完成自动配置,提高了扩展性。在启动时使用观察者模式,以事件发布的形式通知,降低耦合,易于扩展等等。
SpringBoot启动流程是怎样的相关推荐
- SpringBoot启动流程分析(四):IoC容器的初始化过程
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot启动流程解析
写在前面: 由于该系统是底层系统,以微服务形式对外暴露dubbo服务,所以本流程中SpringBoot不基于jetty或者tomcat等容器启动方式发布服务,而是以执行程序方式启动来发布(参考下图ke ...
- Springboot启动流程分析(四):完成启动流程
目录 一 添加BeanPostProcessors到IOC容器 二 国际化支持 三 初始化监听器的多路播放器 四 刷新容器 五 注册监听器到IOC容器的多播器 六 完成bean的大规模实例化 6.1 ...
- SPRINGBOOT启动流程及其原理
Spring Boot.Spring MVC 和 Spring 有什么区别? 分别描述各自的特征: Spring 框架就像一个家族,有众多衍生产品例如 boot.security.jpa等等:但他们的 ...
- SpringBoot启动流程简要
SpringBoot启动流程大概: 初始化SpringApplication 根据项目的配置情况和Conditional条件来推断是否是一个Web应用. 读取所有jar包下面spring.factor ...
- 高级面试题--SpringBoot启动流程解析
写在前面: 由于该系统是底层系统,以微服务形式对外暴露dubbo服务,所以本流程中SpringBoot不基于jetty或者tomcat等容器启动方式发布服务,而是以执行程序方式启动来发布(参考下图ke ...
- SpringBoot入门到精通-SpringBoot启动流程(七)
定义自己的starter SpringBoot入门到精通-Spring的注解编程(一) SpringBoot入门到精通-SpringBoot入门(二) SpringBoot入门到精通-Spring的基 ...
- [springboot]springboot启动流程
Spring Boot程序有一个入口,就是main方法.main里面调用SpringApplication.run()启动整个Spring Boot程序,该方法所在类需要使用@SpringBootAp ...
- Java面试--SpringBoot启动流程
一.SpringBoot是什么 SpringBoot 是依赖于 Spring 的,比起 Spring,除了拥有 Spring 的全部功能以外,SpringBoot 无需繁琐的 Xml 配置,这取决于它 ...
- Java springboot启动流程
springboot启动流程 一.简述 二.注解 三.启动 1. 运行 SpringApplication.run() 方法 2. 确定应用程序启动类型 3. 加载所有的初始化器 4. 加载所有的监听 ...
最新文章
- android xUtils的使用
- 图片被遮住一部分能复原吗_真的准确吗?就是这张图片自称能检测出你的眼睛近视不近视...
- 关于STM32的两个小问题的总结
- 简单高效地控制高亮度LED
- 算法59----打家劫舍【动态规划】
- 为什么要特征标准化及特征标准化方法
- vue使用百度地图获取位置信息
- 百度竞价推广怎么做?需要注意哪些?
- 一、Chrome浏览器调试工具/文档
- 支付宝支付申请流程,配置过程
- 在mt6735中添加新的开机logo与开\关机动画
- spring data jpa使用的几种方式
- Scala 连接Redis工具类
- 仓库物品领用吉度PDA出入库盘点扫码方案
- java.beans_javabeans是什么
- 暴风影音——去除广告的方法
- 加速度测试什么软件,错题整理神器,喵喵错题APP实现高效学习的第一步
- OSChina 娱乐弹弹弹——程序猿其实很好找女朋友!
- STM32/51单片机实训day8——基于Keil5+Proteus8使用DHT11温度传感器实现温湿度采集并在LM016L液晶屏上显示
- 【前端】Ant Design Pro和Arco Design Pro非技术对比