Spring Boot的启动过程

本文从SpringApplication类开始分析Spring Boot应用的启动过程,使用的Spring Boot版本是1.5.15.RELEASE。

成员变量

public class SpringApplication {
    /**
     * The class name of application context that will be used by default for non-web
     * environments.
     */
    public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
            + "annotation.AnnotationConfigApplicationContext";

    /**
     * The class name of application context that will be used by default for web
     * environments.
     */
    public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
            + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

    private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" };

    /**
     * Default banner location.
     */
    public static final String BANNER_LOCATION_PROPERTY_VALUE = SpringApplicationBannerPrinter.DEFAULT_BANNER_LOCATION;

    /**
     * Banner location property key.
     */
    public static final String BANNER_LOCATION_PROPERTY = SpringApplicationBannerPrinter.BANNER_LOCATION_PROPERTY;

    private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";

    private static final Log logger = LogFactory.getLog(SpringApplication.class);

    private final Set<Object> sources = new LinkedHashSet<Object>();

    private Class<?> mainApplicationClass;

    private Banner.Mode bannerMode = Banner.Mode.CONSOLE;

    private boolean logStartupInfo = true;

    private boolean addCommandLineProperties = true;

    private Banner banner;

    private ResourceLoader resourceLoader;

    private BeanNameGenerator beanNameGenerator;

    private ConfigurableEnvironment environment;

    private Class<? extends ConfigurableApplicationContext> applicationContextClass;

    private boolean webEnvironment;

    private boolean headless = true;

    private boolean registerShutdownHook = true;

    private List<ApplicationContextInitializer<?>> initializers;

    private List<ApplicationListener<?>> listeners;

    private Map<String, Object> defaultProperties;

    private Set<String> additionalProfiles = new HashSet<String>();
}
  • DEFAULT_CONTEXT_CLASS常量表示非Web环境默认的应用上下文是AnnotationConfigApplicationContext类型;
  • DEFAULT_WEB_CONTEXT_CLASS常量表示Web环境下默认的应用上下文是AnnotationConfigEmbeddedWebApplicationContext类型;
  • sources表示bean来源;
  • mainApplicationClass表示启动类类型;
  • resourceLoader表示资源加载的策略;
  • beanNameGenerator表示生成bean名称的策略;
  • environment表示使用的环境;
  • applicationContextClass表示创建的应用上下文的类型;
  • webEnvironment表示当前是否是Web环境;
  • initializers表示添加的ApplicationContextInitializer;
  • listeners表示添加的ApplicationListener;
  • ...

构造函数

SpringApplication类的两个构造函数如下,它们都调用initialize方法做初始化工作。

public SpringApplication(Object... sources) {
    initialize(sources);
}

public SpringApplication(ResourceLoader resourceLoader, Object... sources) {
    this.resourceLoader = resourceLoader;
    initialize(sources);
}

initialize方法代码如下:

@SuppressWarnings({ "unchecked", "rawtypes" })
private void initialize(Object[] sources) {
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
    }
    this.webEnvironment = deduceWebEnvironment();
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

该方法做了如下工作:

  1. deduceWebEnvironment方法检测当前是否是Web环境,只有当Servlet接口和ConfigurableWebApplicationContext接口都存在且能被加载时才是Web环境;
    private boolean deduceWebEnvironment() {
        for (String className : WEB_ENVIRONMENT_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return false;
            }
        }
        return true;
    }
    
  2. getSpringFactoriesInstances方法从jar包的META/spring.factories文件中获取特定类型的工厂实现类限定名并实例化它们,此处是创建了ApplicationContextInitializerApplicationListener两种类型的工厂实现类,并分别保存到initializers和listeners成员变量中。每个jar包都可能有spring.factories文件,以spring-boot-1.5.15.RELEASE.jar中的META/spring.factories为例,该文件包含4种ApplicationContextInitializer和9种ApplicationListener;
    # Application Context Initializers
    org.springframework.context.ApplicationContextInitializer=\
    org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
    org.springframework.boot.context.ContextIdApplicationContextInitializer,\
    org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
    org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer
    
    # Application Listeners
    org.springframework.context.ApplicationListener=\
    org.springframework.boot.ClearCachesApplicationListener,\
    org.springframework.boot.builder.ParentContextCloserApplicationListener,\
    org.springframework.boot.context.FileEncodingApplicationListener,\
    org.springframework.boot.context.config.AnsiOutputApplicationListener,\
    org.springframework.boot.context.config.ConfigFileApplicationListener,\
    org.springframework.boot.context.config.DelegatingApplicationListener,\
    org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
    org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
    org.springframework.boot.logging.LoggingApplicationListener
    
  3. deduceMainApplicationClass方法找出main方法所在的类并保存到mainApplicationClass成员变量。
    private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        }
        catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }
    

run方法

SpringApplication类的run静态方法代码如下所示,可见其调用了上文的构造函数和run成员方法。

public static ConfigurableApplicationContext run(Object source, String... args) {
    return run(new Object[] { source }, args);
}

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    return new SpringApplication(sources).run(args);
}

run成员方法代码如下:

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);
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        Banner printedBanner = printBanner(environment);
        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);
    }
}

run成员方法的主要流程如下:

  1. 创建SpringApplicationRunListener类型的实例;
  2. 为应用上下文准备环境;
  3. 创建应用上下文;
  4. 准备应用上下文;
  5. 刷新应用上下文。

创建SpringApplicationRunListener

在创建SpringApplicationRunListener的过程中,首先getRunListeners方法在各META/spring.factories文件查找org.springframework.boot.SpringApplicationRunListener的工厂实现类,然后以该SpringApplication对象和main方法的命令行参数为实参调用这些实现类的构造函数实例化它们,最后各SpringApplicationRunListener的starting回调方法被调用。

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
            SpringApplicationRunListener.class, types, this, args));
}
  • SpringApplicationRunListener接口专门用于监听run方法,其代码如下所示,该接口的实现类需要声明一个公有的以SpringApplication和字符串数组为参数的构造函数。
    public interface SpringApplicationRunListener {
    
        void starting();
    
        void environmentPrepared(ConfigurableEnvironment environment);
    
        void contextPrepared(ConfigurableApplicationContext context);
    
        void contextLoaded(ConfigurableApplicationContext context);
    
        void finished(ConfigurableApplicationContext context, Throwable exception);
    }
    
  • SpringApplicationRunListeners类是SpringApplicationRunListener的集合,其方法内部都是依次调用各SpringApplicationRunListener同名的回调方法。
    class SpringApplicationRunListeners {
        private final Log log;
        private final List<SpringApplicationRunListener> listeners;
    
        SpringApplicationRunListeners(Log log,
                Collection<? extends SpringApplicationRunListener> listeners) {
            this.log = log;
            this.listeners = new ArrayList<SpringApplicationRunListener>(listeners);
        }
    
        public void starting() {
            for (SpringApplicationRunListener listener : this.listeners) {
                listener.starting();
            }
        }
    
        public void environmentPrepared(ConfigurableEnvironment environment) {
            for (SpringApplicationRunListener listener : this.listeners) {
                listener.environmentPrepared(environment);
            }
        }
    
        // 省略一些代码
    }
    
  • 以spring-boot-1.5.15.RELEASE.jar中的META/spring.factories为例,该文件只包含一个EventPublishingRunListener;
    # Run Listeners
    org.springframework.boot.SpringApplicationRunListener=\
    org.springframework.boot.context.event.EventPublishingRunListener
    
    EventPublishingRunListener类的部分代码如下,它的构造函数将SpringApplication的listeners成员变量保存的各ApplicationListener加入事件广播器中,其他回调方法在内部广播了相应的事件给各ApplicationListener。
    public EventPublishingRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        for (ApplicationListener<?> listener : application.getListeners()) {
            this.initialMulticaster.addApplicationListener(listener);
        }
    }
    
    @Override
    @SuppressWarnings("deprecation")
    public void starting() {
        this.initialMulticaster
                .multicastEvent(new ApplicationStartedEvent(this.application, this.args));
    }
    
    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
                this.application, this.args, environment));
    }
    
    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
    
    }
    
    // 省略一些代码
    

准备环境

准备环境是由prepareEnvironment方法完成的,其代码如下所示。

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,否则创建StandardEnvironment;
    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        if (this.webEnvironment) {
            return new StandardServletEnvironment();
        }
        return new StandardEnvironment();
    }
    
  • 然后是配置环境,分别配置了属性源和配置文件;
    protected void configureEnvironment(ConfigurableEnvironment environment,
            String[] args) {
        configurePropertySources(environment, args);
        configureProfiles(environment, args);
    }
    
    protected void configurePropertySources(ConfigurableEnvironment environment,
            String[] args) {
        MutablePropertySources sources = environment.getPropertySources();
        if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
            sources.addLast(
                    new MapPropertySource("defaultProperties", this.defaultProperties));
        }
        if (this.addCommandLineProperties && args.length > 0) {
            String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
            if (sources.contains(name)) {
                PropertySource<?> source = sources.get(name);
                CompositePropertySource composite = new CompositePropertySource(name);
                composite.addPropertySource(new SimpleCommandLinePropertySource(
                        name + "-" + args.hashCode(), args));
                composite.addPropertySource(source);
                sources.replace(name, composite);
            }
            else {
                sources.addFirst(new SimpleCommandLinePropertySource(args));
            }
        }
    }
    
    protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
        environment.getActiveProfiles(); // ensure they are initialized
        // But these ones should go first (last wins in a property key clash)
        Set<String> profiles = new LinkedHashSet<String>(this.additionalProfiles);
        profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
        environment.setActiveProfiles(profiles.toArray(new String[profiles.size()]));
    }
    
  • 最后各SpringApplicationRunListener的environmentPrepared回调方法被调用。

创建应用上下文

createApplicationContext方法创建上下文,如果applicationContextClass字段指定了上下文类型则创建该类型的应用上下文,否则对Web环境默认创建AnnotationConfigEmbeddedWebApplicationContext,对非Web环境创建AnnotationConfigApplicationContext。

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);
}

所创建的应用上下文即是DispatcherServlet的应用上下文,这与传统的Spring MVC工程不同。

准备应用上下文

prepareContext方法用于准备上面创建的应用上下文,其代码如下所示。

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);
}

该方法主要做了以下几件事:

  • 为创建的应用上下文设置了之前创建的环境;
  • postProcessApplicationContext方法做了创建应用上下文后的处理工作,为应用上下文注册了bean名称生成策略,子类可以重写该方法自定义其他工作;
  • applyInitializers方法按各ApplicationContextInitializer的添加顺序调用其initialize回调方法,请注意除了上文提到jar包的META/spring.factories文件中可以指定该接口的工厂实现类之外,还可以在调用run方法之前通过SpringApplication类的addInitializers成员方法添加ApplicationContextInitializer;
  • 各SpringApplicationRunListener的contextPrepared回调方法被调用;
  • load方法将bean定义加载到应用上下文中(还未实例化);
  • 各SpringApplicationRunListener的contextLoaded回调方法被调用。

刷新应用上下文

refreshContext方法刷新创建的应用上下文,根据上一步加载的bean定义实例化各单例bean。

private void refreshContext(ConfigurableApplicationContext context) {
    refresh(context);
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
}

protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    ((AbstractApplicationContext) applicationContext).refresh();
}

应用上下文刷新后

应用上下文刷新后run方法调用了afterRefresh方法执行所有的Runner,并调用各SpringApplicationRunListener的finished回调方法。

afterRefresh(context, applicationArguments);
listeners.finished(context, null);

afterRefresh方法代码如下所示:

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);
        }
    }
}
  • 首先找出应用上下文中所有的ApplicationRunnerCommandLineRunner,实际使用时这两个接口的实现类用@Component注解修饰即可;
  • 然后对这些ApplicationRunner和CommandLineRunner按升序排序,它们的实现类都可以选择地实现Ordered接口或者用@Order注解修饰;
  • 最后按顺序调用这些Runner的run回调方法。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容