springboot的启动原理和流程
本文基于springboot 2.2.13版本
一、springboot启动的原理
基于springboot的web应用本质上还是一个spring应用,所以也会有一个spring容器,也需要一个web容器。
在以前基于ssm进行开发的时候,使用的web容器是外部的,比如tomcat,整个应用打包后会被部署在外部tomcat容器中,而在springboot应用中使用的是内嵌的web容器。
所以总结下来,springboot的启动过程中涉及一个spring容器和一个内嵌的tomcat。
我们先用一段代码来模拟下springboot启动过程中容器的创建和内嵌tomcat的启动
springboot为web应用提供了一个容器AnnotationConfigServletWebServerApplicationContext
public class BootTest {
public static void main(String[] args) {
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext();
context.register(MyAppConfig.class);
context.refresh();
}
@Configuration
static class MyAppConfig{
// 产生内嵌web容器的bean
@Bean
public ServletWebServerFactory servletWebServerFactory(){
TomcatServletWebServerFactory servletWebServerFactory = new TomcatServletWebServerFactory();
servletWebServerFactory.setPort(8080);
return servletWebServerFactory;
}
}
}
上边的代码先创建了一个spring容器,然后注册了一个配置类,为了方便演示直接把配置类定义成了内部了类。
在配置类中添加了一个ServletWebServerFactory
bean,这个bean从名字就可以看出来是用来创建内嵌web容器的。
运行上边的main方法从控制台打印信息就可以看出来tomcat服务已经正常启动了,只不过没有注册任何web资源到tomcat中,所以不能访问。
下面我们尝试利用配置类给tomcat中注册一些springmvc相关的内容
public class BootTest {
public static void main(String[] args) {
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext();
context.register(MyAppConfig.class);
context.refresh();
}
@Configuration
static class MyAppConfig{
//内嵌tomcat
@Bean
public ServletWebServerFactory servletWebServerFactory(){
TomcatServletWebServerFactory servletWebServerFactory = new TomcatServletWebServerFactory();
servletWebServerFactory.setPort(8080);
return servletWebServerFactory;
}
@Bean
public DispatcherServlet dispatcherServlet(){
return new DispatcherServlet();
}
//spring容器通过这个bean用来注册servlet到内嵌tomcat中
@Bean
public ServletRegistrationBean<DispatcherServlet> servletRegistrationBean(DispatcherServlet dispatcherServlet){
ServletRegistrationBean<DispatcherServlet> registrationBean = new ServletRegistrationBean<DispatcherServlet>();
registrationBean.setServlet(dispatcherServlet);
registrationBean.addUrlMappings("/");
return registrationBean;
}
//注册处理器映射器 RequestMappingHandlerMapping
@Bean
public RequestMappingHandlerMapping handlerMapping(){
return new RequestMappingHandlerMapping();
}
//处理器适配器
@Bean
public RequestMappingHandlerAdapter handlerAdapter(){
RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
// 添加一个转换器 为了处理json数据
handlerAdapter.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
return handlerAdapter;
}
@Bean
public HelloController helloController(){
return new HelloController();
}
}
@RequestMapping("/hello")
static class HelloController{
//返回json数据给客户端
@GetMapping("/test1")
@ResponseBody
public Map<String,String> test1(){
Map<String,String> map = new HashMap<>();
map.put("code","200");
map.put("message","success");
return map;
}
}
}
上边利用配置类给spring容器中配置了springmvc运行需要的四大器和Controller,再次启动main方法就可以通过
http://localhost:8080/hello/test1
来访问内嵌tomcat
那么spring容器是在什么时机利用ServletWebServerFactory
创建tomcat的?
其实是在这个容器AnnotationConfigServletWebServerApplicationContext
的onRefresh
方法中从容器里获取
ServletWebServerFactory
类型的bean并创建web容器,这个方法定义在父类ServletWebServerApplicationContext
中
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
而这个onRefresh方法是在spring容器的refresh方法中调用的
二、springboot的启动流程
上面讲解了springboot应用启动的本质是spring容器+内嵌tomcat,接下来我们来看下一个标准springboot应用的启动流程
一个springboot应用中我们一般会写出这样的启动类
@SpringBootApplication
public class BootTest2 {
public static void main(String[] args) {
SpringApplication.run(BootTest2.class,args);
}
}
所以启动流程就是从SpringApplication.run
这个静态方法开始的,整个流程可以总结为两个大的步骤
2.1 创建一个SpringApplication对象
这个步骤主要做了这几步操作
2.1.1 应用类型推断
这一步会推断应用类型,这个应用类型会决定后边创建的spring容器的类型
2.1.2 创建初始化器
这一步会收集应用中ApplicationContextInitializer.class
这个接口的实现类然后创建对象设置到SpringApplication对象的属性中
2.1.3 创建事件监听器
这一步会收集应用中ApplicationListener.class
这个接口的实现类然后创建对象设置到SpringApplication对象的属性中
2.1.4 主类推断
2.2 执行SpringApplication对象的run方法
在这一步主要做了以下步骤
2.2.1 创建事件发布器
这一步会收集应用中SpringApplicationRunListener.class
这个接口的实现类,然后创建对象,默认情况下springboot只提供了一个实现类EventPublishingRunListener
,可以使用此对象在springboot启动过程中的不同阶段发布事件,为了兼容有多个实现类的情况,springboot使用SpringApplicationRunListeners
对事件发布器类进行了一个包装。
当事件发布器发布了某一个事件时,对应的事件监听器就会收到事件然后执行。
2.2.2 发布starting事件
2.2.3 对启动参数args做一个封装
封装启动参数为ApplicationArguments对象
2.2.4创建并准备环境对象
在这一步中会创建环境对象 ConfigurableEnvironment
,springboot会使用这个对象来读取启动参数,配置文件等配置中的参数,我们常用的application.properties就是通过这个对象读取的。
在这个过程中会发布environmentPrepared
事件,注意给环境对象中配置针对application.properties文件的解析器就是通过ConfigFileApplicationListener
这个监听器监听 environmentPrepared
事件实现的。
2.2.5 打印banner
2.2.6 创建spring容器
根据前边推导的应用类型创建对应的spring容器
2.2.7执行初始化器
执行前边收集到的初始化器,ApplicationContextInitializer
实现类,对spring容器进行加强。
2.2.8 发布contextPrepared事件
2.2.9 加载bean配置信息到spring容器中
调用SpringApplication.load方法加载各种途径的bean配置信息到容器中
2.2.10 发布contextLoaded事件
2.2.11 执行spring容器的refresh方法
容器刷新
2.2.12 发布 started事件
2.2.13 调用Runner接口
从容器中获取 ApplicationRunner
和CommandLineRunner
这两个接口的实现类,然后执行
2.2.14 发布running事件
至此springboot就启动完成了。
关于具体的源码就不贴了,按着这个脉络去看springboot的启动源码就可以对应上了。