SpringBoot启动流程

38次阅读

共计 8410 个字符,预计需要花费 22 分钟才能阅读完成。

1.SpringBoot 项目启动方式:

  1. 在 IDE 中启动 SpringBoot 主类 (XXXApplication) 中的 main 方法
  2. 使用 mvn spring-boot:run 命令启动
  3. 打成 jar 包之后使用 java -jar xxx.jar 运行
  4. 打成 war 包之后放在 web 容器中运行

这是一篇一年多前写的博客,使用的源码版本是 1.5.x。当时发布在 CSDN,现在同步到其他平台,虽然 SpringBoot 这个版本帝刷的很快,但是 2.x 版本的启动流程并没有怎么变化,一样可供参考。

2.SpringBoot 启动流程主要分为三步:

第一部分:SpringApplication 初始化模块, 配置一些基本的环境变量, 资源, 监听器, 构造器;

第二部分: 实现了应用具体的启动方案, 包括流程的监听模块, 加载配置环境模块以及创建上下文环境模块

第三部分: 自动化配置模块, 这个模块是实现 SpringBoot 的自动配置


SpringBoot 程序的主入口就是标注了 @SpringBootApplication 注解的类, 该类中有一个 main 方法, 在 main 方法中调用 SpringApplication 的 run()方法, 这个 run()方法来启动整个程序

@SpringBootApplication
public class CrmWebApiApplication extends SpringBootServletInitializer {public static void main(String[] args) {SpringApplication.run(CrmWebApiApplication.class, args);
    }
    
}

下面是 @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 {

这是一个组合注解, 其中标注的注解主要有以下作用

@EnableAutoConfiguration: 开启 SpringBoot 自动配置, 在程序启动时会自动加载 SpringBoot 的默认配置, 如果有对一些参数进行配置, 则会在程序启动时或调用时进行追加或者覆盖

@SpringBootConfiguration: 这个注解和 @Configuration 注解的作用一样, 用来表示被标注的类是一个配置类, 会将被标注的类中一个或多个被 @Bean 注解修饰的方法添加到 Spring 容器中, 实例的名字默认是方法名

@ComponentScan: 包扫描注解, 默认扫描主类包路径下的类


进入 run()方法后的代码如下:

/**
     * Static helper that can be used to run a {@link SpringApplication} from the
     * specified sources using default settings and user supplied arguments.
     * @param sources the sources to load
     * @param args the application arguments (usually passed from a Java main method)
     * @return the running {@link ApplicationContext}
     */
    public static ConfigurableApplicationContext run(Object[] sources, String[] args) {return new SpringApplication(sources).run(args);
    }

这里会创建一个 SpringApplication 类的实例, 进入 SpringApplication 类中可以看到构造方法里调用了一个 initialize(sources)方法

/**
     * Create a new {@link SpringApplication} instance. The application context will load
     * beans from the specified sources (see {@link SpringApplication class-level}
     * documentation for details. The instance can be customized before calling
     * {@link #run(String...)}.
     * @param sources the bean sources
     * @see #run(Object, String[])
     * @see #SpringApplication(ResourceLoader, Object...)
     */
    public SpringApplication(Object... sources) {initialize(sources);
    }

Initialize(sources)方法源码如下:

@SuppressWarnings({"unchecked", "rawtypes"})
private void initialize(Object[] sources) {if (sources != null && sources.length > 0) {
        // 将 sources 设置到 SpringApplication 类的 source 属性中,这时的 source 值只有主类
        this.sources.addAll(Arrays.asList(sources));
    }
    // 判断是不是 web 程序,this.webEnvironment = deduceWebEnvironment();
    // 从 spring.factories 文件中找出 key 为 ApplicationContextInitializer 的类进行实例化,然后设置到 SpringApplciation 类的 initializers 属性中,这个过程也是找出所有的应用程序初始化器
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 从 spring.factories 文件中找出 key 为 ApplicationListener 的类并实例化后设置到 SpringApplication 的 listeners 属性中。这个过程就是找出所有的应用程序事件监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 找出 main 类,也就是 SpringBoot 项目的主类
    this.mainApplicationClass = deduceMainApplicationClass();}

2.1 run 方法完整代码

执行完初始化之后回到 run()方法中,完整代码如下:

/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();
  stopWatch.start();
  ConfigurableApplicationContext context = null;
  FailureAnalyzers analyzers = null;
  configureHeadlessProperty();
   // 创建应用监听器
  SpringApplicationRunListeners listeners = getRunListeners(args);
  // 开始监听
  listeners.starting();
  
  try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
     // 加载 SpringBoot 配置环境 ConfigurableEnvironment,见 2.2 配置 ConfigurableEnvironment
     ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
     // 打印 banner
     Banner printedBanner = printBanner(environment);
     // 创建应用程序上下文,见 2.3 创建应用程序上下文
     context = createApplicationContext();
     analyzers = new FailureAnalyzers(context);
     prepareContext(context, environment, listeners, applicationArguments,printedBanner);
     refreshContext(context);
     afterRefresh(context, applicationArguments);
     listeners.finished(context, null);
     stopWatch.stop();
     if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass)
            .logStarted(getApplicationLog(), stopWatch);
     }
     return context;
   }catch (Throwable ex) {handleRunFailure(context, listeners, analyzers, ex);
     throw new IllegalStateException(ex);
   }
}

2.2 配置 ConfigurableEnvironment

加载 SpringBoot 配置环境 ConfigurableEnvironment 流程如下:

private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        listeners.environmentPrepared(environment);
        if (!this.webEnvironment) {environment = new EnvironmentConverter(getClassLoader())
                    .convertToStandardEnvironmentIfNecessary(environment);
        }
        return environment;
    }

在加载配置环境的过程中会判断是否是 web 容器启动,如果是容器启动会加载 StandardServletEnvironment

private ConfigurableEnvironment getOrCreateEnvironment() {if (this.environment != null) {return this.environment;}
        if (this.webEnvironment) {return new StandardServletEnvironment();
        }
        return new StandardEnvironment();}

StandardServletEnvironment 类的继承关系如下,StandardServletEnvironment

PropertyResolver 接口是用于解析任何基础源的属性的接口,在加载完配置之后会将配置环境加入到监听器对象 SpringApplicationRunListeners 中。

2.3 创建应用程序上下文

然后会创建应用上下文对象,具体代码如下:

protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                contextClass = Class.forName(this.webEnvironment
                        ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
            }
            catch (ClassNotFoundException ex) {
                throw new IllegalStateException(
                        "Unable create a default ApplicationContext,"
                                + "please specify an ApplicationContextClass",
                        ex);
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
    }

方法会先显式的获取应用上下文对象,如果对象为空,再加载默认的环境配置,通过是否是 webEnvironment 进行判断,默认选择的是 AnnotationConfigApplicationContext(注解上下文,通过扫秒注解来加载 bean),然后通过 BeanUtils 来实例化应用上下文对象然后返回,ConfigurableApplicationContext 类继承关系如下:

这里推荐一下我的另一篇博客,不太懂 ConfigurableApplicationContext 的可以去看一下,https://juejin.im/post/5d7205…

回到 run()方法中,会调用 prepareContext()方法将 environment, listeners,applicationArguments, printedBanner 等组件与上下文对象进行关联

private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {context.setEnvironment(environment);
        postProcessApplicationContext(context);
        applyInitializers(context);
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }

        // Add boot specific singleton beans
        context.getBeanFactory().registerSingleton("springApplicationArguments",
                applicationArguments);
        if (printedBanner != null) {context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }

        // Load the sources
        Set<Object> sources = getSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[sources.size()]));
        listeners.contextLoaded(context);
    }

然后会调用 refreshContext()方法,实际调用 org.springframework.context.support.AbstractApplicationContext.refresh()内的相关方法。这个方法里会进行 redis,mybatis 等的自动配置,包括 spring.factories 的加载,bean 的实例化,BenFactoryPostProcessor 接口的执行,BeanPostProcessor 接口的执行,条件注解的解析,国际化功能的初始化等。

refreshContext()方法执行完毕之后会执行 afterRefresh 方法,当 run()方法执行完之后 Spring 容器也就初始化完毕了

protected void afterRefresh(ConfigurableApplicationContext context,
            ApplicationArguments args) {callRunners(context, args);
    }

    private void callRunners(ApplicationContext context, ApplicationArguments args) {List<Object> runners = new ArrayList<Object>();
        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        AnnotationAwareOrderComparator.sort(runners);
        for (Object runner : new LinkedHashSet<Object>(runners)) {if (runner instanceof ApplicationRunner) {callRunner((ApplicationRunner) runner, args);
            }
            if (runner instanceof CommandLineRunner) {callRunner((CommandLineRunner) runner, args);
            }
        }
    }

    private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
        try {(runner).run(args);
        }
        catch (Exception ex) {throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
        }
    }

    private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
        try {(runner).run(args.getSourceArgs());
        }
        catch (Exception ex) {throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
        }
    }

正文完
 0