前言

本文将分析spring boot应用在启动时的几个关键时间点,学习Spring Boot的运行原理。

版本:Spring Boot 1.5.x

Applcation.main()

当我们执行Applcation.main()方法时,它会创建一个SpringApplication实例,并将该类作为参数传递。

springApplication.initialize()

创建SpringApplication实例时会执行其初始化方法:

private void initialize(Object[] sources) {
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
    }
    this.webEnvironment = deduceWebEnvironment();//检测是否是web环境
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));//添加上下文初始化器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();//添加监听器
}
private void initialize(Object[] sources) {
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
    }
    this.webEnvironment = deduceWebEnvironment();//检测是否是web环境
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));//添加上下文初始化器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();//添加监听器
}

这些初始化器和监听器大部分与用户没有交际,这里可以先忽略。只有一个监听器需要了解:

spring boot 父子工程 jekins 只编译某一个子工程_java

该监听器会负责加载Spring Boot的配置文件。

springApplication.run()

然后会调用SpringApplication实例的启动方法,该方法是启动过程的核心方法,需要重点关注:

public ConfigurableApplicationContext run(String... args) {
    //...
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);//准备参数,为main函数传递的参数
    ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);//准备环境参数,包括系统属性、系统环境变量、应用属性等
    context = createApplicationContext();//创建应用上下文对象
    prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);//为应用上下文启动做准备
    refreshContext(context);//启动应用上下文
    afterRefresh(context, applicationArguments);
    listeners.finished(context, null);
    //...
}
public ConfigurableApplicationContext run(String... args) {
    //...
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);//准备参数,为main函数传递的参数
    ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);//准备环境参数,包括系统属性、系统环境变量、应用属性等
    context = createApplicationContext();//创建应用上下文对象
    prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);//为应用上下文启动做准备
    refreshContext(context);//启动应用上下文
    afterRefresh(context, applicationArguments);
    listeners.finished(context, null);
    //...
}

springApplication.prepareContext()

该方法会做一些准备工作,大部分目前可以忽略,重点是会加载我们的Application类到应用上下文中。

load(context, sources.toArray(new Object[sources.size()]));

我们的Application默认添加了@SpringBootApplication注解,即作为入口配置类,将承担配置的作用。

springApplication.refreshContext()

该方法将启动应用上下文,加载配置类并完成bean的自动扫描、自动装配功能。以及完成嵌入式Servlet容器的创建、启动,ServletContext的配置(如添加Servlet和Filter)等工作。

embeddedWebApplicationContext.refresh()

对于嵌入式web容器的应用上下文来讲,它的主要执行流程是:

1.执行应用上下文的的基本任务,加载配置类并完成bean的创建和装配工作:

public final void refresh() throws BeansException, IllegalStateException {
    try {
        super.refresh();//主要是创建并装配bean
    }
    catch (RuntimeException ex) {
        stopAndReleaseEmbeddedServletContainer();
        throw ex;
    }
}
public final void refresh() throws BeansException, IllegalStateException {
    try {
        super.refresh();//主要是创建并装配bean
    }
    catch (RuntimeException ex) {
        stopAndReleaseEmbeddedServletContainer();
        throw ex;
    }
}

2.创建嵌入式Servlet容器,并添加回调函数:

private void createEmbeddedServletContainer() {
    EmbeddedServletContainer localContainer = this.embeddedServletContainer;
    ServletContext localServletContext = getServletContext();
    if (localContainer == null && localServletContext == null) {
        EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
        this.embeddedServletContainer = containerFactory
                .getEmbeddedServletContainer(getSelfInitializer());//创建嵌入式Servlet容器并添加启动回调函数
    }
    else if (localServletContext != null) {
        try {
            getSelfInitializer().onStartup(localServletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context",
                    ex);
        }
    }
    initPropertySources();
}
private void createEmbeddedServletContainer() {
    EmbeddedServletContainer localContainer = this.embeddedServletContainer;
    ServletContext localServletContext = getServletContext();
    if (localContainer == null && localServletContext == null) {
        EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
        this.embeddedServletContainer = containerFactory
                .getEmbeddedServletContainer(getSelfInitializer());//创建嵌入式Servlet容器并添加启动回调函数
    }
    else if (localServletContext != null) {
        try {
            getSelfInitializer().onStartup(localServletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context",
                    ex);
        }
    }
    initPropertySources();
}

3.嵌入式Servlet容器在创建时会自动启动容器,参考TomcatEmbeddedServletContainer.initialize(),其启动时会调用刚才的回调函数:

this.tomcat.start();//启动tomcat
this.tomcat.start();//启动tomcat

4.执行回调函数,执行ServletContextInitializer接口定义的初始化任务。

private void selfInitialize(ServletContext servletContext) throws ServletException {
    prepareEmbeddedWebApplicationContext(servletContext);//将应用上下文添加到servletContxt中
    registerApplicationScope(servletContext);
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(),
            servletContext);
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext);//执行初始化任务,如添加Servlet,Filter
    }
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
    prepareEmbeddedWebApplicationContext(servletContext);//将应用上下文添加到servletContxt中
    registerApplicationScope(servletContext);
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(),
            servletContext);
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext);//执行初始化任务,如添加Servlet,Filter
    }
}

ServletContextInitializer是spring boot定义的接口,用于初始化ServletContext,主要用于添加Servlet,Filter实例,在Spring Boot上使用SpringMVC时,dispatcherServlet就是通过这种方法默认添加的,参考DispatcherServletAutoConfigration

5.完成Servlet容器的启动,监听请求并提供服务。

protected void finishRefresh() {
    super.finishRefresh();
    EmbeddedServletContainer localContainer = startEmbeddedServletContainer();//完成嵌入式Servlet容器的启动
    if (localContainer != null) {
        publishEvent(
                new EmbeddedServletContainerInitializedEvent(this, localContainer));
    }
}
protected void finishRefresh() {
    super.finishRefresh();
    EmbeddedServletContainer localContainer = startEmbeddedServletContainer();//完成嵌入式Servlet容器的启动
    if (localContainer != null) {
        publishEvent(
                new EmbeddedServletContainerInitializedEvent(this, localContainer));
    }
}

总结

本篇大致梳理了以jar包方式,运行Spring MVC时,Spring Boot的启动原理。其主要步骤是:

  • 读取配置属性
  • 创建应用上下文,读取入口配置类,创建并装配Bean。这个过程中,所有满足条件的start模块会生效。后端模块也是在这个过程中完成初始化,并工作就绪的。
  • 创建并开始启动嵌入式Serlvet容器。在嵌入式容器的启动过程中,会触发回调函数,以Java代码的方式配置Web相关功能,如配置Servlet,Filter,然后完成启动。