美文网首页Spring-Boot
springboot 2.0启动流程源码分析(一)

springboot 2.0启动流程源码分析(一)

作者: kaizhi | 来源:发表于2018-12-24 15:51 被阅读0次

    使用https://start.spring.io/生成一个简单demo,版本选择2.1.1

    image.png
    或者我们从github上下载下来springboot的源码,解压缩后在spring-boot-samples文件夹可以找到很多springboot的示例程序,可以方便地用来学习
    image.png

    我们这里选择spring-boot-sample-simple进行源码分析
    如果不想将整个spring-boot-sample都导入idea的话,我们需要调整一下spring-boot-sample-simple的pom文件,可以将pom中的parent替换成如下内容:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
    </parent>
    

    先从创建示例中的main函数开始读起:

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

    可以看到main函数很简单,只是调用了一个SpringApplication.run,其中args可以传递启动时需要的个性化参数。我们跳转到源码

    /**
         * Static helper that can be used to run a {@link SpringApplication} from the
         * specified source using default settings.
         * @param primarySource the primary source to load
         * @param args the application arguments (usually passed from a Java main method)
         * @return the running {@link ApplicationContext}
         */
        public static ConfigurableApplicationContext run(Class<?> primarySource,
                String... args) {
            return run(new Class<?>[] { primarySource }, args);
        }
    

    可以看到run的返回值是一个ApplicationContext类型的,用过spring的对于这个类都会很熟悉。继续跟踪run的源码,发现这是run的一个重载方法,Class<?>[] primarySources允许一次启动多个类。

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

    先来看SpringApplication(primarySources)

    /**
         * Create a new {@link SpringApplication} instance. The application context will load
         * beans from the specified primary sources (see {@link SpringApplication class-level}
         * documentation for details. The instance can be customized before calling
         * {@link #run(String...)}.
         * @param primarySources the primary bean sources
         * @see #run(Class, String[])
         * @see #SpringApplication(ResourceLoader, Class...)
         * @see #setSources(Set)
         */
        public SpringApplication(Class<?>... primarySources) {
            this(null, primarySources);
        }
    

    通过传入primarySources参数构造了一个SpringApplication类,继续看这个构造函数

    /**
         * Create a new {@link SpringApplication} instance. The application context will load
         * beans from the specified primary sources (see {@link SpringApplication class-level}
         * documentation for details. The instance can be customized before calling
         * {@link #run(String...)}.
         * @param resourceLoader the resource loader to use
         * @param primarySources the primary bean sources
         * @see #run(Class, String[])
         * @see #setSources(Set)
         */
        @SuppressWarnings({ "unchecked", "rawtypes" })
        public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
            this.resourceLoader = resourceLoader;
            Assert.notNull(primarySources, "PrimarySources must not be null");
            this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
            this.webApplicationType = WebApplicationType.deduceFromClasspath();
            setInitializers((Collection) getSpringFactoriesInstances(
                    ApplicationContextInitializer.class));
            setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
            this.mainApplicationClass = deduceMainApplicationClass();
        }
    

    在这里主要是初始化了SpringApplication的私有属性:
    resourceLoader
    primarySources
    webApplicationType //web application的类型
    initializers
    listeners
    mainApplicationClass
    以上创建了一个SpringApplication实例,并对实例的属性进行了设置,接下来看一下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;
            Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
            configureHeadlessProperty();
            SpringApplicationRunListeners listeners = getRunListeners(args);
            listeners.starting();
            try {
                ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                        args);
                ConfigurableEnvironment environment = prepareEnvironment(listeners,
                        applicationArguments);
                configureIgnoreBeanInfo(environment);
                Banner printedBanner = printBanner(environment);
                context = createApplicationContext();
                exceptionReporters = getSpringFactoriesInstances(
                        SpringBootExceptionReporter.class,
                        new Class[] { ConfigurableApplicationContext.class }, context);
                prepareContext(context, environment, listeners, applicationArguments,
                        printedBanner);
                refreshContext(context);
                afterRefresh(context, applicationArguments);
                stopWatch.stop();
                if (this.logStartupInfo) {
                    new StartupInfoLogger(this.mainApplicationClass)
                            .logStarted(getApplicationLog(), stopWatch);
                }
                listeners.started(context);
                callRunners(context, applicationArguments);
            }
            catch (Throwable ex) {
                handleRunFailure(context, ex, exceptionReporters, listeners);
                throw new IllegalStateException(ex);
            }
    
            try {
                listeners.running(context);
            }
            catch (Throwable ex) {
                handleRunFailure(context, ex, exceptionReporters, null);
                throw new IllegalStateException(ex);
            }
            return context;
        }
    

    首先启动stopWatch,这个工具类用来对应用的启动进行计时,我们在控制台看到的springboot启动时间就是通过stopWatch计算的;
    接着设置系统属性“java.awt.headless”,headless模式是在缺少显示屏、键盘或者鼠标是的系统配置,不设置的话默认值就被设置为true;
    接下来的过程比较重要,分别是:
    1.获取并启动监听器
    监听器的获取是new出来了一个SpringApplicationRunListeners实例并返回,通过跟踪代码我们知道了spring boot会获取META-INF/spring.factories中的资源,并创建这些资源的实例(listeners监听器),然后为每一个监听器创建一个线程启动起来;


    image.png

    2.读取了命令行的参数
    3.构造容器环境
    4.创建容器ApplicationContext,有三种ApplicationContext:
    (1)AnnotationConfigServletWebServerApplicationContext,用于web环境;
    (2)AnnotationConfigReactiveWebServerApplicationContext,用于reactive web环境;
    (3)AnnotationConfigApplicationContext,用于非web环境。
    5.实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
    6.准备容器
    7.刷新容器
    8.刷新容器后的扩展接口
    接下来的文章我们会对以上步骤具体分析。

    相关文章

      网友评论

        本文标题:springboot 2.0启动流程源码分析(一)

        本文链接:https://www.haomeiwen.com/subject/nsdzkqtx.html