前言

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启动流程是怎样的相关推荐

  1. SpringBoot启动流程分析(四):IoC容器的初始化过程

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  2. SpringBoot启动流程解析

    写在前面: 由于该系统是底层系统,以微服务形式对外暴露dubbo服务,所以本流程中SpringBoot不基于jetty或者tomcat等容器启动方式发布服务,而是以执行程序方式启动来发布(参考下图ke ...

  3. Springboot启动流程分析(四):完成启动流程

    目录 一 添加BeanPostProcessors到IOC容器 二 国际化支持 三 初始化监听器的多路播放器 四 刷新容器 五 注册监听器到IOC容器的多播器 六 完成bean的大规模实例化 6.1 ...

  4. SPRINGBOOT启动流程及其原理

    Spring Boot.Spring MVC 和 Spring 有什么区别? 分别描述各自的特征: Spring 框架就像一个家族,有众多衍生产品例如 boot.security.jpa等等:但他们的 ...

  5. SpringBoot启动流程简要

    SpringBoot启动流程大概: 初始化SpringApplication 根据项目的配置情况和Conditional条件来推断是否是一个Web应用. 读取所有jar包下面spring.factor ...

  6. 高级面试题--SpringBoot启动流程解析

    写在前面: 由于该系统是底层系统,以微服务形式对外暴露dubbo服务,所以本流程中SpringBoot不基于jetty或者tomcat等容器启动方式发布服务,而是以执行程序方式启动来发布(参考下图ke ...

  7. SpringBoot入门到精通-SpringBoot启动流程(七)

    定义自己的starter SpringBoot入门到精通-Spring的注解编程(一) SpringBoot入门到精通-SpringBoot入门(二) SpringBoot入门到精通-Spring的基 ...

  8. [springboot]springboot启动流程

    Spring Boot程序有一个入口,就是main方法.main里面调用SpringApplication.run()启动整个Spring Boot程序,该方法所在类需要使用@SpringBootAp ...

  9. Java面试--SpringBoot启动流程

    一.SpringBoot是什么 SpringBoot 是依赖于 Spring 的,比起 Spring,除了拥有 Spring 的全部功能以外,SpringBoot 无需繁琐的 Xml 配置,这取决于它 ...

  10. Java springboot启动流程

    springboot启动流程 一.简述 二.注解 三.启动 1. 运行 SpringApplication.run() 方法 2. 确定应用程序启动类型 3. 加载所有的初始化器 4. 加载所有的监听 ...

最新文章

  1. android xUtils的使用
  2. 图片被遮住一部分能复原吗_真的准确吗?就是这张图片自称能检测出你的眼睛近视不近视...
  3. 关于STM32的两个小问题的总结
  4. 简单高效地控制高亮度LED
  5. 算法59----打家劫舍【动态规划】
  6. 为什么要特征标准化及特征标准化方法
  7. vue使用百度地图获取位置信息
  8. 百度竞价推广怎么做?需要注意哪些?
  9. 一、Chrome浏览器调试工具/文档
  10. 支付宝支付申请流程,配置过程
  11. 在mt6735中添加新的开机logo与开\关机动画
  12. spring data jpa使用的几种方式
  13. Scala 连接Redis工具类
  14. 仓库物品领用吉度PDA出入库盘点扫码方案
  15. java.beans_javabeans是什么
  16. 暴风影音——去除广告的方法
  17. 加速度测试什么软件,错题整理神器,喵喵错题APP实现高效学习的第一步
  18. OSChina 娱乐弹弹弹——程序猿其实很好找女朋友!
  19. STM32/51单片机实训day8——基于Keil5+Proteus8使用DHT11温度传感器实现温湿度采集并在LM016L液晶屏上显示
  20. 【前端】Ant Design Pro和Arco Design Pro非技术对比

热门文章

  1. 设置Qt应用程序图标
  2. Salesforce 小知识:大量“子记录”的处理方法
  3. CPU profiling
  4. windows平台搭建Mongo数据库复制集(类似集群)(一)
  5. JS - 讨论 - 编码习惯 - JavaScript代码到底要不要写分号?
  6. 使用java获取本机mac
  7. 常用Redis命令总结
  8. PHP 5.3-5.5 新特性
  9. 嵌入式系统 Boot Loader 技术内幕
  10. 64位服务器IIS不能识别32位framework版本。IIS没有Asp.net切换界面的解决办法。