SpringBoot启动流程探究

Spring的丰富生态备受开发者青睐,尤其是自从SpringBoot出现之后去掉了原来的复杂配置,因为SpringBoot的理念就是约定大于配置,这让我们省去了很多需要手动配置的过程,就拿SpringMVC来说吧各种XML配置直接劝退初学者,但是SpringBoot的易用性简直是成为了推广Spring生态的利器。本篇文章主要是结合SpringBoot的源码,来探究SpringBoot应用程序的启动流程!

新建一个SpringBoot项目,首先映入眼帘的恐怕就是下面的这个关键的Main函数与@SpringBootApplication注解吧,我们将从这个注解开始,逐步探究SpringBoot应用的启动流程:

1@SpringBootApplication
2public class SpringBootStartApplication {
3
4    public static void main(String[] args) {
5        SpringApplication.run(SpringBootStartApplication.class, args);
6    }
7
8}

@SpringBootApplication

@SpringBootApplication注解实际上是SpringBoot提供的一个复合注解,我们来看一看其源码:

 1@Target(ElementType.TYPE)
 2@Retention(RetentionPolicy.RUNTIME)
 3@Documented
 4@Inherited
 5@SpringBootConfiguration
 6@EnableAutoConfiguration
 7@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
 8		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
 9public @interface SpringBootApplication {
10    ...
11}

关于这里面某些元注解的功能,可以参考我之前的写的一篇博客 《 注解的原理与实现 》 。在这里我们只需要看@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan这三个注解。在 SpringBoot 应用的启动类上用这个三个注解代替@SpringBootApplication注解其实也是没问题的:

 1@SpringBootConfiguration
 2@EnableAutoConfiguration
 3@ComponentScan
 4public class SpringBootStartApplication {
 5
 6    public static void main(String[] args) {
 7        SpringApplication.run(SpringBootStartApplication.class, args);
 8    }
 9
10}

那我们接下来就需要分贝探究这三个注解的功能。

@SpringBootConfiguration

 1@Target({ElementType.TYPE})
 2@Retention(RetentionPolicy.RUNTIME)
 3@Documented
 4@Configuration
 5public @interface SpringBootConfiguration {
 6    @AliasFor(
 7        annotation = Configuration.class
 8    )
 9    boolean proxyBeanMethods() default true;
10}

@SpringBootConfiguration也是来源于@Configuration,二者功能都是将当前类标注为配置类,@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建Bean定义,初始化Spring容器,这个貌似一点都不新奇。

@EnableAutoConfiguration

1@Target(ElementType.TYPE)
2@Retention(RetentionPolicy.RUNTIME)
3@Documented
4@Inherited
5@AutoConfigurationPackage
6@Import(AutoConfigurationImportSelector.class)
7public @interface EnableAutoConfiguration {
8    ...
9}

@EnableAutoConfiguration 注解启用自动配置,其中最关键的要属@Import(AutoConfigurationImportSelector.class),借AutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。

借助于Spring框架原有的一个工具类:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自动配置功效才得以大功告成!

mark

关于这个注解可以参考我的另一篇文章 《SpringBoot自动配置原理》 ,里面有详细介绍并且有例子。

@ComponentScan

@ComponentScan 对应于XML配置形式中的 context:component-scan,用于将一些标注了特定注解的bean定义批量采集注册到Spring的IoC容器之中,这些特定的注解大致包括:

  • @Controller
  • @Entity
  • @Component
  • @Service
  • @Repository

对于该注解可以通过 basePackages 属性来更细粒度的控制该注解的自动扫描范围,比如:

1@ComponentScan(basePackages = {"xpu.tim.controller","xpu.tim.entity"})

SpringApplication对象构造流程

@SpringBootApplication这个注解看完了, 那么接下来就来看看这个SpringApplication以及run()方法究竟干了些啥。原始的SpringCore中并没有这个类,SpringApplication里面封装了一套Spring 应用的启动流程,然而这对用户完全透明,因此我们上手 SpringBoot 时感觉简洁、轻量。

通过阅读run方法的源码我们不难发现,其实是需要构造一个SpringApplication对象:

 1/**
 2 * Static helper that can be used to run a {@link SpringApplication} from the
 3 * specified sources using default settings and user supplied arguments.
 4 * @param primarySources the primary sources to load
 5 * @param args the application arguments (usually passed from a Java main method)
 6 * @return the running {@link ApplicationContext}
 7 */
 8public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
 9    return new SpringApplication(primarySources).run(args);
10}

默认的 SpringApplication执行流程已经可以满足大部分需求,但是若用户想干预这个过程,则可以通过SpringApplication在流程某些地方开启的扩展点来完成对流程的扩展,典型的扩展方案那就是使用 set 方法。

 1@SpringBootApplication
 2public class SpringBootStartApplication {
 3    public static void main(String[] args) {
 4        //SpringApplication.run(SpringBootStartApplication.class, args);
 5        SpringApplication application = new SpringApplication(SpringBootStartApplication.class);
 6        application.set...(); // 用户自定义扩展点
 7        application.set...(); // 用户自定义扩展点
 8        application.run(args);
 9    }
10}

这样一拆解后我们发现,我们也需要先构造 SpringApplication 类对象,然后调用该对象的 run() 方法。那么接下来就讲讲 SpringApplication 的构造过程以及其 run() 方法的流程,搞清楚了这个,那么也就搞清楚了SpringBoot应用是如何运行起来的! 主要需要看以下四个方法:

mark

1、deduceFromClasspath:用来推断应用的类型:创建的是 REACTIVE应用、SERVLET应用、NONE 三种中的一种

mark

NONE表示当前的应用即不是一个web应用也不是一个REACTIVE应用,是一个纯后台的应用。SERVLET表示当前应用是一个标准的web应用。REACTIVE是spring5当中的新特性,表示是一个响应式的web应用。而判断的依据就是根据Classloader中加载的类。如果是servlet,则表示是web,如果是DispatcherHandler,则表示是一个REACTIVE应用,如果两者都不存在,则表示是一个非web环境的应用。

2、setInitializers:使用SpringFactoriesLoader查找并加载classpath下 META-INF/spring.factories文件中所有可用的 ApplicationContextInitializer

mark

使用 SpringFactoriesLoader查找并加载classpath下META-INF/spring.factories文件中的所有可用的 ApplicationListener

3、setListeners:使用 SpringFactoriesLoader查找并加载classpath下 META-INF/spring.factories文件中的所有可用的 ApplicationListener

mark

4、deduceMainApplicationClass:推断并设置main方法的定义类

mark

通过这个几个关键步骤,SpringApplication完成了实例化。

run()方法探究

之前我们弄清楚了SpringApplication的实例化过程,现在看看它的run方法究竟干了什么:

mark

1、通过 SpringFactoriesLoader 加载META-INF/spring.factories文件,获取并创建SpringApplicationRunListener对象;

2、然后由SpringApplicationRunListener来发出starting消息;

3、把参数args封装成DefaultApplicationArguments,并配置当前SpringBoot应用将要使用的Environment;

4、完成之后,依然由SpringApplicationRunListener来发出 environmentPrepared(环境已准备)消息;

5、创建上下文,根据项目类型创建上下文;

6、初始化ApplicationContext,并设置 Environment,加载相关配置等;

7、由SpringApplicationRunListener来发出contextPrepared消息,告知SpringBoot应用使用的ApplicationContext已准备OK;

8、将各种 beans 装载入ApplicationContext,继续由SpringApplicationRunListener来发出contextLoaded消息,告知 SpringBoot 应用使用的ApplicationContext已装填OK;

9、refresh ApplicationContext,完成IoC容器可用的最后一步;

10、由SpringApplicationRunListener来发出started消息 ;

11、完成最终的程序的启动;

12、SpringApplicationRunListener来发出running消息,告知程序已运行起来了;

步骤4和5之间还有个PrintBanner,用来打印Banner

createApplicationContext()

下面这段代码主要是根据项目类型创建上下文,并且会注入几个核心组件类:

 1protected ConfigurableApplicationContext createApplicationContext() {
 2    Class<?> contextClass = this.applicationContextClass;
 3    if (contextClass == null) {
 4        try {
 5            switch (this.webApplicationType) {
 6                case SERVLET:
 7                    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
 8                    break;
 9                case REACTIVE:
10                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
11                    break;
12                default:
13                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
14            }
15        }
16        catch (ClassNotFoundException ex) {
17            throw new IllegalStateException(
18                "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
19        }
20    }
21    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
22}

Web类型项目创建上下文对象AnnotationConfigServletWebServerApplicationContext 。这里会把ConfigurationClassPostProcessor 、AutowiredAnnotationBeanPostProcessor 等一些核心组件加入到Spring容器。

refreshContext()

下面一起来看下refreshContext(context) 这个方法,这个方法启动spring的代码加载了bean,还启动了内置web容器:

 1private void refreshContext(ConfigurableApplicationContext context) {
 2    refresh(context);
 3    if (this.registerShutdownHook) {
 4        try {
 5            context.registerShutdownHook();
 6        }
 7        catch (AccessControlException ex) {
 8            // Not allowed in some environments.
 9        }
10    }
11}

点击跟进后发现方法里面是spring容器启动代码:

mark

我们可以看到一个onRefresh方法,点进去需要看的是子类实现,我们只看其中一个子类实现:

mark

 1@Override
 2protected void onRefresh() {
 3    super.onRefresh();
 4    try {
 5        createWebServer();
 6    }
 7    catch (Throwable ex) {
 8        throw new ApplicationContextException("Unable to start web server", ex);
 9    }
10}
11
12private void createWebServer() {
13    WebServer webServer = this.webServer;
14    ServletContext servletContext = getServletContext();
15    if (webServer == null && servletContext == null) {
16        // 这个获取webServerFactory还是要进去看看
17        ServletWebServerFactory factory = getWebServerFactory();
18        this.webServer = factory.getWebServer(getSelfInitializer());
19    }
20    else if (servletContext != null) {
21        try {
22            getSelfInitializer().onStartup(servletContext);
23        }
24        catch (ServletException ex) {
25            throw new ApplicationContextException("Cannot initialize servlet context", ex);
26        }
27    }
28    initPropertySources();
29}

我们继续看下getWebServletFactory() 这个方法,这个里面其实就是选择出哪种类型的web容器了:

 1protected ServletWebServerFactory getWebServerFactory() {
 2    // Use bean names so that we don't consider the hierarchy
 3    String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
 4    if (beanNames.length == 0) {
 5        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
 6                                              + "ServletWebServerFactory bean.");
 7    }
 8    if (beanNames.length > 1) {
 9        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
10                                              + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
11    }
12    return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
13}

我们再去看factory.getWebServer(getSelfInitializer()) ,转到定义就会看到很熟悉的名字tomcat:

mark

内置的Servlet容器就是在onRefresh()方法里面启动的,至此一个Servlet容器就启动OK了。

SpringBoot启动过程简述

1、new了一个SpringApplication对象,使用SPI技术加载加载 ApplicationContextInitializer、ApplicationListener 接口实例;

2、调用SpringApplication.run()方法;

3、调用createApplicationContext()方法创建上下文对象,创建上下文对象同时会注册spring的核心组件类(ConfigurationClassPostProcessor 、AutowiredAnnotationBeanPostProcessor 等);

4、调用refreshContext() 方法启动Spring容器和内置的Servlet容器;

mark