• SpringBoot学习笔记(4)启动过程原理


    前言

    SpringBoot为我们做的自动配置,确实方便快捷,但是对于新手来说,如果不大懂SpringBoot内部启动原理,以后难免会吃亏。所以这次博主就跟你们一起一步步揭开SpringBoot的神秘面纱,让它不在神秘。

    深入探索SpringApplication执行流程

    SpringApplication的run方法的实现是我们本次旅程的主要线路,该方法的主要流程大体可以归纳如下:

    1) 如果我们使用的是SpringApplication的静态run方法,那么,这个方法里面首先要创建一个SpringApplication对象实例,然后调用这个创建好的SpringApplication的实例方法。在SpringApplication实例初始化的时候,它会提前做几件事情:

    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
            return (new SpringApplication(primarySources)).run(args);
        }
    • 根据classpath里面是否存在某个特征类(org.springframework.web.context.ConfigurableWebApplicationContext)来决定是否应该创建一个为Web应用使用的ApplicationContext类型。
    • 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer。
    • 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。
    • 推断并设置main方法的定义类。
    public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.addConversionService = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = new HashSet();
        this.isCustomEnvironment = false;
        this.lazyInitialization = false;
        this.resourceLoader = resourceLoader;
        //断言主配置不为空  如果为空则抛出异常
        Assert.notNull(primarySources, "PrimarySources must not be null");
        //保存主配置类
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        //确定web应用类型
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        //使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer。
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        //使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        //推断并设置main方法的定义类。
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

    2) SpringApplication实例初始化完成并且完成设置后,就开始执行run方法的逻辑了,方法执行开始,首先遍历执行所有通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListener。调用它们的started()方法,告诉这些SpringApplicationRunListener,“嘿,SpringBoot应用要开始执行咯!”。

    public ConfigurableApplicationContext run(String... args) {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            ConfigurableApplicationContext context = null;
            Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
            this.configureHeadlessProperty();
            //1.通过SpringFactoriesLoader查找并加载所有的SpringApplicationRunListeners,通过调用
            //starting()方法通知所有的SpringApplicationRunListeners:应用开始启动了
            SpringApplicationRunListeners listeners = this.getRunListeners(args);
            listeners.starting();
    
            Collection exceptionReporters;
            try {
                //2.创建并配置当前应用将要使用的Environment
                ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
                this.configureIgnoreBeanInfo(environment);
                //3.打印banner
                Banner printedBanner = this.printBanner(environment);
                //4.根据是否是web项目,来创建不同的ApplicationContext容器
                context = this.createApplicationContext();
                //5.创建一系列FailureAnalyzer
                exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
                //6.初始化ApplicationContext
                this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
                //7.调用ApplicationContext的refresh()方法,刷新容器
                this.refreshContext(context);
                //8.查找当前context中是否注册有CommandLineRunner和ApplicationRunner,如果有则遍历执行它们。
                this.afterRefresh(context, applicationArguments);
                stopWatch.stop();
                if (this.logStartupInfo) {
                    (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
                }
    
                listeners.started(context);
                this.callRunners(context, applicationArguments);
            } catch (Throwable var10) {
                this.handleRunFailure(context, var10, exceptionReporters, listeners);
                throw new IllegalStateException(var10);
            }
    
            try {
                listeners.running(context);
                return context;
            } catch (Throwable var9) {
                this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
                throw new IllegalStateException(var9);
            }
        }

    1).SpringApplicationRunListeners其本质上就是一个事件发布者,它在SpringBoot应用启动的不同时间点发布不同应用事件类型(ApplicationEvent)

    public interface SpringApplicationRunListener {
    
        // 运行run方法时立即调用此方法,可以用户非常早期的初始化工作
        void starting();
        
        // Environment准备好后,并且ApplicationContext创建之前调用
        void environmentPrepared(ConfigurableEnvironment environment);
    
        // ApplicationContext创建好后立即调用
        void contextPrepared(ConfigurableApplicationContext context);
    
        // ApplicationContext加载完成,在refresh之前调用
        void contextLoaded(ConfigurableApplicationContext context);
    
        // 当run方法结束之前调用
        void finished(ConfigurableApplicationContext context, Throwable exception);
    
    }

    2). 创建并配置当前Spring Boot应用将要使用的Environment。

    Environment用于描述应用程序当前的运行环境,其抽象了两个方面的内容:配置文件(profile)和属性(properties),不同的环境(eg:生产环境、预发布环境)可以使用不同的配置文件,而属性则可以从配置文件、环境变量、命令行参数等来源获取。因此,当Environment准备好后,在整个应用的任何时候,都可以从Environment中获取资源。
    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
            ConfigurableEnvironment environment = this.getOrCreateEnvironment();
            this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
            ConfigurationPropertySources.attach((Environment)environment);
            listeners.environmentPrepared((ConfigurableEnvironment)environment);
            this.bindToSpringApplication((ConfigurableEnvironment)environment);
            if (!this.isCustomEnvironment) {
                environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
            }
    
            ConfigurationPropertySources.attach((Environment)environment);
            return (ConfigurableEnvironment)environment;
        }

    2.1). 遍历调用所有SpringApplicationRunListener的environmentPrepared()的方法,告诉他们:“当前SpringBoot应用使用的Environment准备好了咯!”。

    void environmentPrepared(ConfigurableEnvironment environment) {
            Iterator var2 = this.listeners.iterator();
    
            while(var2.hasNext()) {
                SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();
                listener.environmentPrepared(environment);
            }
    
        }

    3). 如果SpringApplication的showBanner属性被设置为true,则打印banner。

    private Banner printBanner(ConfigurableEnvironment environment) {
            if (this.bannerMode == Mode.OFF) {
                return null;
            } else {
                ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader : new DefaultResourceLoader(this.getClassLoader());
                SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter((ResourceLoader)resourceLoader, this.banner);
                return this.bannerMode == Mode.LOG ? bannerPrinter.print(environment, this.mainApplicationClass, logger) : bannerPrinter.print(environment, this.mainApplicationClass, System.out);
            }
        }

    4).根据是否是web项目,来创建不同的ApplicationContext容器.将之前准备好的Environment设置给创建好的ApplicationContext使用。

    protected ConfigurableApplicationContext createApplicationContext() {
            Class<?> contextClass = this.applicationContextClass;
            if (contextClass == null) {
                try {
                    switch(this.webApplicationType) {
                    case SERVLET:
                        contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                        break;
                    case REACTIVE:
                        contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                        break;
                    default:
                        contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
                    }
                } catch (ClassNotFoundException var3) {
                    throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
                }
            }
    
            return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
        }

    5).创建一系列FailureAnalyzer,创建流程依然是通过SpringFactoriesLoader获取到所有实现FailureAnalyzer接口的class,然后在创建对应的实例。FailureAnalyzer用于分析故障并提供相关诊断信息。

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
            ClassLoader classLoader = this.getClassLoader();
            Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
            List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
            AnnotationAwareOrderComparator.sort(instances);
            return instances;
        }

    6).初始化ApplicationContext

    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
            //将准备好的Environment设置给ApplicationContext
            context.setEnvironment(environment);
            this.postProcessApplicationContext(context);
            //遍历调用所有的ApplicationContextInitializer的initialize()方法来对已经创建好的ApplicationContext进行进一步的处理
            this.applyInitializers(context);
            //调用SpringApplicationRunListener的contextPrepared()方法,通知所有的监听者:ApplicationContext已经准备完毕
            listeners.contextPrepared(context);
            if (this.logStartupInfo) {
                this.logStartupInfo(context.getParent() == null);
                this.logStartupProfileInfo(context);
            }
            //将所有的bean加载到容器中
            ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
            beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
            if (printedBanner != null) {
                beanFactory.registerSingleton("springBootBanner", printedBanner);
            }
    
            if (beanFactory instanceof DefaultListableBeanFactory) {
                ((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
            }
    
            if (this.lazyInitialization) {
                context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
            }
    
            Set<Object> sources = this.getAllSources();
            Assert.notEmpty(sources, "Sources must not be empty");
            this.load(context, sources.toArray(new Object[0]));
            //调用SpringApplicationRunListener的contextLoaded()方法,通知所有的监听者:ApplicationContext已经装载完毕
            listeners.contextLoaded(context);
        }

    7).调用ApplicationContext的refresh()方法,刷新容器,完成IoC容器可用的最后一道工序。

    private void refreshContext(ConfigurableApplicationContext context) {
            this.refresh(context);
            if (this.registerShutdownHook) {
                try {
                    context.registerShutdownHook();
                } catch (AccessControlException var3) {
                }
            }
    
        }

    8).查找当前context中是否注册有CommandLineRunner和ApplicationRunner,如果有则遍历执行它们。

    private void callRunners(ApplicationContext context, ApplicationArguments args) {
            List<Object> runners = new ArrayList();
            runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
            runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
            AnnotationAwareOrderComparator.sort(runners);
            Iterator var4 = (new LinkedHashSet(runners)).iterator();
    
            while(var4.hasNext()) {
                Object runner = var4.next();
                if (runner instanceof ApplicationRunner) {
                    this.callRunner((ApplicationRunner)runner, args);
                }
    
                if (runner instanceof CommandLineRunner) {
                    this.callRunner((CommandLineRunner)runner, args);
                }
            }
    
        }

    正常情况下,遍历执行SpringApplicationRunListener的finished()方法、(如果整个过程出现异常,则依然调用所有SpringApplicationRunListener的finished()方法,只不过这种情况下会将异常信息一并传入处理)
    去除事件通知点后,整个流程如下:

    public void finished(ConfigurableApplicationContext context, Throwable exception) {
            for (SpringApplicationRunListener listener : this.listeners) {
                callFinishedListener(listener, context, exception);
            }
        }

    总结

    到此,SpringBoot的核心组件完成了基本的解析,综合来看,大部分都是Spring框架背后的一些概念和实践方式,SpringBoot只是在这些概念和实践上对特定的场景事先进行了固化和升华,而也恰恰是这些固化让我们开发基于Sping框架的应用更加方便高效。

  • 相关阅读:
    LOJ #3219. 「PA 2019」Iloczyny Fibonacciego (斐波拉契表示性质+FFT)
    一类区间修改问题的做法
    [百炼智能]hihoCoder挑战赛37 D Items(树状数组维护01背包—梦想成真!!!)
    LOJ #2092. 「ZJOI2016」大森林(lct)
    LOJ #3220. 「PA 2019」Terytoria(随机染色或线段树)
    Codeforces 223E. Planar Graph(平面图)
    JZOJ 6678. 【2020.05.01省选模拟】苏菲的世界 (simpson积分+几何法求多个圆的并的面积)
    Day3-Python基础3---函数介绍
    Day2-Python基础2---字符编码与转码
    Day2-Python基础2---集合和文件操作
  • 原文地址:https://www.cnblogs.com/mengY/p/11739259.html
Copyright © 2020-2023  润新知