springboot 启动原理

springboot 常见的启动写法如下:

@SpringBootApplicationpublic class Application {    public static void main(String[] args) {        SpringApplication.run(Application.class, args);    }}

然后我们的程序就可以和 main 方法一样,直接启动运行了。

但是这一切是如何实现的呢?


SpringApplication.run 方法

main 方法整体看起来看起来平平无奇。




springboot 判断更新操作是否成功_加载


平平无奇

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

这里实际上是创建了 SpringApplication 对象,并且执行 run 方法。

SpringApplication

我们简单看一下这个对象。

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

这里主要是针对 spring 的初始化:

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

设置了一些初始化实现、监听器等,此处不做详细展开。

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

我们这里大概梳理一下启动过程的步骤:

1. 初始化监听器,以及添加到SpringApplication的自定义监听器。2. 发布ApplicationStartedEvent事件,如果想监听ApplicationStartedEvent事件,你可以这样定义:public class ApplicationStartedListener implements ApplicationListener,然后通过SpringApplication.addListener(..)添加进去即可。3. 装配参数和环境,确定是web环境还是非web环境。4. 装配完环境后,就触发ApplicationEnvironmentPreparedEvent事件。5. 如果SpringApplication的showBanner属性被设置为true,则打印启动的Banner。6. 创建ApplicationContext,会根据是否是web环境,来决定创建什么类型的ApplicationContext。7. 装配Context的环境变量,注册Initializers、beanNameGenerator等。8. 发布ApplicationPreparedEvent事件。9. 注册springApplicationArguments、springBootBanner,加载资源等10. 遍历调用所有SpringApplicationRunListener的contextLoaded()方法。11. 调用ApplicationContext的refresh()方法,装配context beanfactory等非常重要的核心组件。12. 查找当前ApplicationContext中是否注册有CommandLineRunner,如果有,则遍历执行它们。13. 发布ApplicationReadyEvent事件,启动完毕,表示服务已经可以开始正常提供服务了。通常我们这里会监听这个事件来打印一些监控性质的日志,表示应用正常启动了。


springboot 判断更新操作是否成功_加载_02


启动流程

@SpringBootApplication 注解

看完了静态方法,我们来看一下另一个注解 @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 {    // 省略方法}

我们省略掉对应的方法属性,发现实际上这个注解是由 3 个注解组合而成:

@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

其中 @SpringbootConfiguration 是完全等价于 @Configuration 的,此处应该是为了和 spring 的注解做区分。

所以一开始的实现,等价于:

@Configuration@EnableAutoConfiguration@ComponentScanpublic class Application {    public static void main(String[] args) {        SpringApplication.run(Application.class, args);    }}

当然了, springboot 的理念就是极简配置,能少写一行代码,就少写一行代码!

@Configuration 注解

这里的 @Configuration 大家应该并不陌生,spring 中可以使用下面的写法,替代 spring xml 的配置写法:

@Configurationpublic class MockConfiguration{    @Bean    public MockService mockService(){        return new MockServiceImpl(dependencyService());    }    @Bean    public DependencyService dependencyService(){        return new DependencyServiceImpl();    }}

@ComponentScan 注解

@ComponentScan 的功能其实就是自动扫描并加载符合条件的组件(比如 @Component 和 @Service等)或者bean定义,最终将这些bean定义加载到IoC容器中。

我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明 @ComponentScan 所在类的package进行扫描。

ps: 所以我们的 Application 启动类一般是放在根目录,这样连扫描的包也省略掉了。

@EnableAutoConfiguration 注解

这个注解我们放在最后讲解,因为它为 springboot 带来了更多的便利性。

注解定义

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(EnableAutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration {}

这个注解实际上是一个组合注解 @AutoConfigurationPackage + @Import

@AutoConfigurationPackage:自动配置包

@Import: 导入自动配置的组件

我们来看一下这 2 个注解:

@AutoConfigurationPackage 注解

这个注解主要是通过 @Import 注解导入了 AutoConfigurationPackages.Registrar.class 类。

实现如下:

/** * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing * configuration. * @author 老马啸西风 */@Order(Ordered.HIGHEST_PRECEDENCE)static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {    @Override    public void registerBeanDefinitions(AnnotationMetadata metadata,            BeanDefinitionRegistry registry) {        register(registry, new PackageImport(metadata).getPackageName());    }    @Override    public Set determineImports(AnnotationMetadata metadata) {        return Collections.singleton(new PackageImport(metadata));    }}

它其实是注册了一个Bean的定义。

new PackageImport(metadata).getPackageName(),它其实返回了当前主程序类的同级以及子级的包组件。

@Import(EnableAutoConfigurationImportSelector.class)

我们来看一下另外一个注解,@Import(EnableAutoConfigurationImportSelector.class)。

EnableAutoConfigurationImportSelector 实现如下:

public class EnableAutoConfigurationImportSelector        extends AutoConfigurationImportSelector {    @Override    protected boolean isEnabled(AnnotationMetadata metadata) {        if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {            return getEnvironment().getProperty(                    EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,                    true);        }        return true;    }}

这个方法一眼看上去也是平平无奇,因为核心实现都在父类中。

最核心的方法如下:

@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {    // 不启用,直接返回无导入    if (!isEnabled(annotationMetadata)) {        return NO_IMPORTS;    }    try {        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader                .loadMetadata(this.beanClassLoader);        AnnotationAttributes attributes = getAttributes(annotationMetadata);        // 这一行回去加载 springboot 指定的文件        List configurations = getCandidateConfigurations(annotationMetadata,                attributes);        configurations = removeDuplicates(configurations);        configurations = sort(configurations, autoConfigurationMetadata);        Set exclusions = getExclusions(annotationMetadata, attributes);        checkExcludedClasses(configurations, exclusions);        configurations.removeAll(exclusions);        configurations = filter(configurations, autoConfigurationMetadata);        fireAutoConfigurationImportEvents(configurations, exclusions);        return configurations.toArray(new String[configurations.size()]);    }    catch (IOException ex) {        throw new IllegalStateException(ex);    }}

我们用过的各种 springboot-starter,使用起来引入一个 jar 就可以使用了。

都要归功于下面这个方法:

// 这一行回去加载 springboot 指定的文件List configurations = getCandidateConfigurations(annotationMetadata, attributes);

这里实际上回去解析一个文件:

Enumeration urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");

这也就是我们在开发自己的 springboot-starter 时,为什么需要把自己的启动类放在 META-INF/spring.factories 文件中的原因,这样就可以被 springboot 加载,并且生效了。

推荐阅读:

Spring Boot-11-自定义 springboot starter

小结

到这里,springboot 的启动原理就讲解的差不多了。

springboot 和以前的 spring xml 配置相比较,确实简化了太多太多。

让我们可以更加快速,正确的启动一个 java web 程序。