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容器,然后注册了一个配置类,为了方便演示直接把配置类定义成了内部了类。

在配置类中添加了一个ServletWebServerFactorybean,这个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的?

其实是在这个容器AnnotationConfigServletWebServerApplicationContextonRefresh方法中从容器里获取

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接口

从容器中获取 ApplicationRunnerCommandLineRunner这两个接口的实现类,然后执行

2.2.14 发布running事件

至此springboot就启动完成了。

关于具体的源码就不贴了,按着这个脉络去看springboot的启动源码就可以对应上了。