美文网首页
Spring Boot 启动流程

Spring Boot 启动流程

作者: RojerAlone | 来源:发表于2019-10-10 23:16 被阅读0次

    Spring Boot 启动流程

    [TOC]

    Spring Boot 的程序启动于 SpringApplicationrun 方法,一步步追踪。

    创建 SpringApplication 实例

    最常见的方法是 SpringApplication.run(Xxx.class, args),在这个方法中,先创建了一个 SpringApplication 实例,再调用 run 方法启动 Spring。

    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class<?>[] { primarySource }, args);
    }
    
    
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return new SpringApplication(primarySources).run(args);
    }
    
    public SpringApplication(Class<?>... primarySources) {
        this(null, primarySources);
    }
    
    @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));
        // 根据 classpath 中存在的类推断出启动的程序是 web 程序还是 webflux 或者普通 java 程序
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        // 获取 ApplicationContextInitializer 的实现类,在 spring.factories 文件中定义的
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        // 同上,获取 ApplicationListener 实现类
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 推断 main class,可见创建 SpringApplication 实例的类不一定是执行 main 方法的类
        this.mainApplicationClass = deduceMainApplicationClass();
    }
    

    从构造方法中可以看到 spring boot 只是获取了一些配置信息,其中有些有意思的东西。

    推断应用类型

    之前做项目想启动一个 rpc 服务提供者,并不需要启动一个内嵌的 Tomcat 容器,先去搜了一下发现是一个很奇葩的做法,实现 CommandLineRunner 接口并重写 run() 方法,Google 搜到的前几条都是这么说的,想了下 Spring Boot 怎么会使用这么蠢的方法?

    正确的方法是 classpath 中不存在相关的类,或者在调用 run 方法之前使用 SpringApplication.setWebApplicationType(WebApplicationType.NONE) 明确指定是一个普通的 Java 进程。

    来看下 Spring Boot 是怎么实现自动推断应用类型的:

    static WebApplicationType deduceFromClasspath() {
        // 存在 org.springframework.web.reactive.DispatcherHandler 类
        // 不存在 org.springframework.web.servlet.DispatcherServlet 和 org.glassfish.jersey.servlet.ServletContainer
        // 就是 Reactive Web 程序
        if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
                && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        // javax.servlet.Servlet 和 org.springframework.web.context.ConfigurableWebApplicationContext 都不存在就是普通 Java 程序
        for (String className : SERVLET_INDICATOR_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }
    

    也就是从 classpath 中获取是否存在相应类型的类,Reactive 需要的类是在 spring-webflux 包中的,而 ConfigurableWebApplicationContext 是在 spring-web 包中,那么只要项目中不引入这两个包就会启动一个普通的 Java 进程了。

    可见所谓的“技术博客”、“技术论坛”多么不靠谱了,"Show me your code",一切都在代码里。(对 Spring 来说它的文档足够丰富,大多数情况不看代码也行)

    spring.factories 文件

    推断过应用类型以后,加载 ApplicationContextInitializerApplicationListener 的实现类,是从 SpringApplication 的 classpath 下 spring.factories 中加载的:

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
        return getSpringFactoriesInstances(type, new Class<?>[] {});
    }
    
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = getClassLoader();
        // 在 LinkedHashSet 中存放类名,保证实例唯一和有序
        // SpringFactoriesLoader.loadFactoryNames 里边只是读取了 spring.factories 中的 k-v 对并返回 type 为 key 的类名
        Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        // 根据 order 排序
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }
    
    // 只是实例化类,在上面的步骤中获取到的都是无参构造方法
    @SuppressWarnings("unchecked")
    private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
            ClassLoader classLoader, Object[] args, Set<String> names) {
        List<T> instances = new ArrayList<>(names.size());
        for (String name : names) {
            try {
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                Assert.isAssignable(type, instanceClass);
                Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
                T instance = (T) BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            }
            catch (Throwable ex) {
                throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
            }
        }
        return instances;
    }
    

    Spring Framework 中提供了 SpringFactoriesLoader 工具类,用于从 spring.factories 文件中读取出某些特性的实现类,而 Spring Boot 也是利用这一点,从 spring.facotry 中读取出 EnableAutoConfiguration 的相关类,从而实现自动化配置。

    这个机制本质上和 Java 的 SPI 没有区别。

    run 方法启动 Spring Boot 程序

        public ConfigurableApplicationContext run(String... args) {
            StopWatch stopWatch = new StopWatch(); // 计时器,用于统计启动时间
            stopWatch.start();
            ConfigurableApplicationContext context = null;
            Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
            configureHeadlessProperty();
            // 获取 SpringApplication 的 listener 并通知 SpringApplication 开始启动了
            SpringApplicationRunListeners listeners = getRunListeners(args);
            listeners.starting();
            try {
                ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                // 配置 environment
                ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
                configureIgnoreBeanInfo(environment);
                // Spring Boot Banner 的打印,可以设置关闭或自定义 Banner
                Banner printedBanner = printBanner(environment);
                // 创建 ApplicationContext,会根据应用类型创建,如果是非 Web 应用则创建一个 AnnotationConfigApplicationContext 实例
                context = createApplicationContext();
                // 获取异常报告器,用于启动失败时打印错误信息
                exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                        new Class[] { ConfigurableApplicationContext.class }, context);
                // 准备 applicaton context,包括
                //   调用构造方法中获取的 ApplicationContextInitializer 的 initialize 方法
                //   通知 listener contextPrepared 事件
                //   将启动类注册为 Spring Bean
                //   通知 listener contextLoaded 事件
                prepareContext(context, environment, listeners, applicationArguments, printedBanner);
                // 经典的 ApplicationContext.refresh 的调用
                refreshContext(context);
                // 暂时是个空方法
                afterRefresh(context, applicationArguments);
                stopWatch.stop();
                // 打印启动信息,主要是应用信息以及启动耗时
                if (this.logStartupInfo) {
                    new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
                }
                // 通知 listener SpringApplication 已经启动成功了
                listeners.started(context);
                // 所有步骤执行完毕,最后执行 ApplicationRunner 和 CommandLineRunner
                callRunners(context, applicationArguments);
            }
            catch (Throwable ex) {
                // 如果抛出异常,就打印错误信息并抛到外层
                handleRunFailure(context, ex, exceptionReporters, listeners);
                throw new IllegalStateException(ex);
            }
    
            try {
                // 通知 listener SpringApplication 正在运行
                listeners.running(context);
            }
            catch (Throwable ex) {
                handleRunFailure(context, ex, exceptionReporters, null);
                throw new IllegalStateException(ex);
            }
            return context;
        }
    

    至此,SpringApplication 的启动流程就走完了,Spring Boot 的自动化配置特性并不是自己单独开发的,而是基于 Spring 的 Import 机制,但是 Import 进来的 Selector 是在 Spring Boot 项目中实现的,后面分析。

    相关文章

      网友评论

          本文标题:Spring Boot 启动流程

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