springboot启动流程1--启动前的准备

  • 1 前言

本文是一个系列,整个启动流程比较长,涉及到配置的初始化,环境的初始化,以及bean的加载流程等。本系列将从分阶段的逐步进行解析。
文章基于springboot2.3.x系列的源码,github的源码与实际发版的可能略微不同,不过整理流程差别不大。
本人第一次写文章,如有错误或误导欢迎留言指正。文章整体可能比较啰嗦,尽可能的将流程中的每一个重要的方法都讲到。

  • 2 SpringApplication初始化

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        //基础资源启动类,默认为当前启动类
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        //判断web服务类型,现在不是推出了reactive web嘛 区别一下
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        //获取ApplicationContextInitializer子类包括自定义的类,用于在applicationContext容器刷新之前调用
    //在prepareContext方法中通过applyInitializers对上下文加载器进行定制化的操作
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        //从堆栈信息中推断当前启动的类
        //感兴趣的也可以了解一下
        this.mainApplicationClass = deduceMainApplicationClass();
}

getSpringFactoriesInstances,从方法的名称可以大概知道是获取ApplicationContextInitializer(应用上下文初始化)和 ApplicationListener 实例工厂,根据传入类的类型。

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

getSpringFactoriesInstances的内部又引出了两个方法,loadFactoryNames和createSpringFactoriesInstances。
下面逐个进行分析。

loadFactoryNames:

    public static List loadFactoryNames(Class factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        // 这里使用Map的getOrDefault方法 平时很少使用,将默认值提前设置进去,在map中取不到结果的时候返回默认值。
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }
    
     private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
    // 优先从缓存中获取
    MultiValueMap result = (MultiValueMap)cache.get(classLoader);
    // 通过classLoader获取jar中的spring.factories文件路径
    Enumeration urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
    // 以下为主要代码
    // 循环遍历url结果
    UrlResource resource = new UrlResource(url);
    // 通过路径加载配置, 将配置内的属性转换成Map的形式
    // Properties为HashMap的子类
    Properties properties = PropertiesLoaderUtils.loadProperties(resource);

    // 遍历map 对value串通过“,”截取成list
    // 遍历list将其存入LinkedMultiValueMap中 注意这里的LinkedMultiValueMap为MultiValueMap子类 其顶层依然是一个
    // map不过内部的value是一个list
    LinkedMultiValueMap result = new LinkedMultiValueMap();
    // 缓存类加载器作为key 缓存结果,减少后续加载
    cache.put(classLoader, result);
    return result;
}

实现过或看过springboot的start包的小伙伴应该豁然开朗了,在start类的jar包下会有个spring.factories文件,记录着配置类。
那也就是在这里对这些文件进行了扫描加载并将路径存放到缓存内。
另外,从这里也能看到其实spring用了很多java底层的东西,比如这里的classloader.getSystemResources,自定义Map类等。经常看源码可能了解到很多没用过的骚操作。

这里留一个问题,为何源码中将classloader作为参数层层传递下去,而不是直接在当前类中使用this.getClassloader?

到此只是找到了这些需要加载的类,但是还未加载。

createSpringFactoriesInstances:

private  List createSpringFactoriesInstances(Class type, Class[] parameterTypes, ClassLoader classLoader, Object[] args, Set names) {
    // 获取class对象,这个classUtils感兴趣的也可以了解一下 
    // 其内部在启动时候初始化了一部分java基础的提升加载速度
    Class instanceClass = ClassUtils.forName(name, classLoader);
    Assert.isAssignable(type, instanceClass);
    //获取一个指定参数类型的构造函数
    Constructor constructor = instanceClass.getDeclaredConstructor(parameterTypes);
    // 通过构造函数和参数 获取一个实例
    // instantiateClass 方法感兴趣的也可以去了解一下,内部使用了spring自身的反射工具类,这里能了解到更多的反射应用
    // 而不仅仅是class.forname 有空的话这些工具类的方法和使用也会做一下,这里面才有很多java底层的应用。而且相对很规范
    T instance = BeanUtils.instantiateClass(constructor, args);
}

到此为止,从spring.factories文件中加载了全部的配置,并且初始化了与ApplicationContextInitializer(应用上下文初始化)ApplicationListener相关的实例工厂。至于它们的作用,我们继续向下看。

这里强调一下,这里是实例化相当于java的new关键字,且并没有放入spring的bean容器中。

  • 3 run方法执行

上run方法源码前,这里在提一下,不同的版本,加载上下文(即初始化bean)前后的方法可能不太相同,但一般影响不大。

源码:

//基础的run方法
public ConfigurableApplicationContext run(String... args) {
    //spring的计时器  以后可以用这个实现,可记录多个任务时间
    StopWatch stopWatch = new StopWatch();
    //开启计时器
    stopWatch.start();
    //初始化上下文应用
    ConfigurableApplicationContext context = null;
    //初始化异常报告集合
    Collection exceptionReporters = new ArrayList();
    //配置系统参数 java.awt.headless ,简单的来说就是告诉服务需要在没有外设和一些硬件的方式下运行,和图形处理有关。
    this.configureHeadlessProperty();
    //监听器集合发布事件,注意这个是一个异步事件
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    //在run()方法开始执行时,该方法就立即被调用,可用于在初始化最早期时做一些工作
    //SpringApplicationRunListener中的每个方法都有调用的阶段。
    listeners.starting();
    Collection exceptionReporters;
    try {
        //初始化应用默认参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        //初始化准备环境
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        //创建打印类
        Banner printedBanner = this.printBanner(environment);
        //创建应用上下文
        context = this.createApplicationContext();
        //准备异常报告器
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
        //准备应用上下文
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        //刷新应用上下文
        this.refreshContext(context);
        //上下文后置处理
        this.afterRefresh(context, applicationArguments);
        //计时器结束
        stopWatch.stop();
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }
        //应用上下文启动后执行监听器
        listeners.started(context);
        //执行所有的runner,CommandLineRunner&ApplicationRunner
        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);
    }
}

run包含了整个启动的流程,本节只讲解到banner的打印,并简单的说一下如何个性化的定制自己专属的启动图文,虽然没有卵用,但是快乐呀!

选出本节关注的内容:

 //spring的计时器  以后可以用这个实现,可记录多个任务时间
    StopWatch stopWatch = new StopWatch();
    //开启计时器
    stopWatch.start();
    //初始化上下文应用
    ConfigurableApplicationContext context = null;
    //初始化异常报告集合
    Collection exceptionReporters = new ArrayList();
    //配置系统参数 java.awt.headless ,简单的来说就是告诉服务需要在没有外设和一些硬件的方式下运行,和图形处理有关。
    this.configureHeadlessProperty();
    //监听器集合发布事件,注意这个是一个异步事件
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    //在run()方法开始执行时,该方法就立即被调用,可用于在初始化最早期时做一些工作
    //SpringApplicationRunListener中的每个方法都有调用的阶段。
    listeners.starting();
    Collection exceptionReporters;

        //初始化应用默认参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        //初始化准备环境
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        //创建打印类
        Banner printedBanner = this.printBanner(environment);

从计时器StopWatch开始,想想spring框架的贡献者是什么样的大佬,怎么可会直接使用System.currentTimeMillis()来计算呢!这个原理并不复杂,内部使用的也是System.nanoTime精度更高的纳秒,而且在很多开源的工具包中都有提供,比如我们常用的Apache的commons,hutool等,但内部的方法并不完全相同,spring提供的更像跑步比赛时的计时器,可以停止多次。

configureHeadlessProperty :设置java.awt.headless属性,告诉java应用程序,当前运行环境缺少键盘、鼠标、显示器等设备,你得自己坚强。

监听器:

 SpringApplicationRunListeners listeners = this.getRunListeners(args);
 listeners.starting();

这里就知道在初始化阶段,获取监听器是做啥用的了,记住这里的监听器,是为了整个启动流程服务的。不是我们日常中自定义的用来处理异步事件的监听器,它们实现的接口是不同的,这里实现的是SpringApplicationRunListener 从名称就很容易看出,是为了上下文启动使用的,而我们常用的是 ApplicationListener
。另外特别强调一下starting不能理解为启动监听器,而是执行与启动前准备工作相关的监听器。
这点通过源码可以很容易理解:

public interface SpringApplicationRunListener {
    void starting();

    void environmentPrepared(ConfigurableEnvironment environment);

    void contextPrepared(ConfigurableApplicationContext context);

    void contextLoaded(ConfigurableApplicationContext context);

    void started(ConfigurableApplicationContext context);

    void running(ConfigurableApplicationContext context);

    void failed(ConfigurableApplicationContext context, Throwable exception);
}

除了starting之外还有environmentPrepared、contextPrepared、contextLoaded、started、runing等。对应启动流程的各个阶段。

关于监听器内部的原理这里也不展开说了,单拿出来还能水一篇哈哈!挖坑挖坑!

因此在自定义start启动jar的时候,若要使用SpringApplicationRunListener,则需要明确自己所想应用的阶段。

到这为止,还都算很简单。下面开始稍微有那么一内内复杂的。默认参数初始化,虽然我们在使用run方法的时候几乎不会传入参数,但人家支持呀!那就要了解一下!

 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

ApplicationArguments 对象本身没有什么好说的,提供应用上下文的参数初始化。但它有一个内部类

 private static class Source extends SimpleCommandLinePropertySource {
    ...
    ...
}

看到这个父类的名称,应该就能猜到这个与PropertySource有关,那我们在确认一下类的关系图。

未命名图片.png

关系图上我们可以大致了解到,这里可以接受run方法传入的参数作为comandline的命令参数,这里将参数封装成PropertySource对象并放入 ApplicationArguments 中以备使用。
参数最终都是通过构造函数super关键字传递到顶层的PropertySource中。

关于SimpleCommandLinePropertySource 这个就不展开说了,这里的确是命令行参数设置,例如: --name=zhangsan,另外这里的设置可以通过@Value获取到,这属于骚操作的范围了吧,很少这么使用。这里可以替换配置文件中的属性,比如在application.properties中的server.port属性可以在这里通过参数--server.port的替换,本质上它是作为启动参数来执行的,相当于 java -jar --server.port=8080 优先级高于配置文件。
入参的格式也需要注意,--开头和非 --开头的解析方式不同这里需要格外注意。

    public PropertySource(String name, T source) {
        this.logger = LogFactory.getLog(this.getClass());
        Assert.hasText(name, "Property source name must contain at least one character");
        Assert.notNull(source, "Property source must not be null");
        this.name = name;
        this.source = source;
    }

PropertySource 也是一个spring中比较重要的类,它是顶层的配置资源封装类,在很多地方都有应用,与BeanDefinition一样,作为spring的规范应用的一种,对扩展使用很方便。

prepareEnvironment : 准备启动环境
源码:

    ConfigurableEnvironment environment = this.prepareEnvironment(listeners,applicationArguments);
    this.configureIgnoreBeanInfo(environment);
    Banner printedBanner = this.printBanner(environment);

这里有两个部分:初始化环境配置和打印banner

初始化环境配置源码:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
    //根据web应用类型获取环境
    ConfigurableEnvironment environment = this.getOrCreateEnvironment();
    //配置环境参数和profiles.active信息
    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;
}

老样子,逐个分析。
getOrCreateEnvironment:获取环境类

    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        } else {
            switch(this.webApplicationType) {
            case SERVLET:
                return new StandardServletEnvironment();
            case REACTIVE:
                return new StandardReactiveWebEnvironment();
            default:
                return new StandardEnvironment();
            }
        }
    }

webApplicationType: 参数在SpringApplication构造函数内(部分源码可能是创建时就直接初始化了)初始化的。

这里返回的结果一般都是StandardServletEnvironment 目前我们未使用响应式web框架,即spring 5开始推出的webflux.这点以后也需要学习一下。

StandardServletEnvironment的父类AbstractEnvironment的构造函数中也进行了配置资源MutablePropertySources(环境配置资源保存)和propertyResolver(资源解析)的初始化,获取了系统配置和环境变量等配置信息,后面用到了再说再细说。

configureEnvironment:配置环境

    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        if (this.addConversionService) {
            ConversionService conversionService = ApplicationConversionService.getSharedInstance();
            environment.setConversionService((ConfigurableConversionService)conversionService);
        }

        this.configurePropertySources(environment, args);
        this.configureProfiles(environment, args);
    }

ConversionService:为环境设置转换服务,这里配置参数addConversionService默认为true,因此这里必定会初始化。

ApplicationConversionService.getSharedInstance()也并不复杂,从名称可以知道为上下文转换服务,这里 getSharedInstance 通过懒汉式的方式获取了一个ApplicationConversionService的单例。

在构造函数内初始化了一些转换和格式化服务。比如String转number,Date转String等。也是配置文件读取时,属性转到配置类中或@value注解绑定参数等地方使用。

扩展说一下单例模式的实现,饿汉式和懒汉式,正常的情况下 懒汉式是非线程安全的。而饿汉式是天生的线程安全。因此若使用懒汉式,需要解决线程安全问题。这里getSharedInstance示范了一个标准的线程安全懒汉式模式之一,比如还可以通过枚举类实现。

下面我们看一下getSharedInstance源码:

    public static ConversionService getSharedInstance() {
        ApplicationConversionService sharedInstance = ApplicationConversionService.sharedInstance;
        if (sharedInstance == null) {
            Class var1 = ApplicationConversionService.class;
            synchronized(ApplicationConversionService.class) {
                sharedInstance = ApplicationConversionService.sharedInstance;
                if (sharedInstance == null) {
                    sharedInstance = new ApplicationConversionService();
                    ApplicationConversionService.sharedInstance = sharedInstance;
                }
            }
        }

        return sharedInstance;
    }

这个扯远了,但是真心觉得源码中,代码的编写和一些java原生工具的使用都非常好,很多细小的点都值得学习。不仅仅只为了弄清一个spring的启动流程,更多的要看大佬们是如何编写代码的。

继续,获取转换服务后设置到环境对象中以备使用。

configurePropertySources 配置资源处理。

 protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
        // 这个属性在AbstractEnvironment的构造函数中初始化
        MutablePropertySources sources = environment.getPropertySources();
        //这里defaultProperties 默认为空
        if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
            sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
        }
        // 前面说到的commandline 配置参数
        if (this.addCommandLineProperties && args.length > 0) {
            String name = "commandLineArgs";
            if (sources.contains(name)) {
                PropertySource source = sources.get(name);
                CompositePropertySource composite = new CompositePropertySource(name);
                composite.addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
                composite.addPropertySource(source);
                sources.replace(name, composite);
            } else {
                sources.addFirst(new SimpleCommandLinePropertySource(args));
            }
        }

    }

这里将传入的配置资源以及默认的配置资源存入环境的资源中。

顺便吐槽一下,原来spring中也有很多写死的常量属性,虽大部分情况下这种属性相对不会改变。但个人还是有点想吐槽!

ConfigurationPropertySources.attach:设置:configurationProperties配置属性

    public static void attach(Environment environment) {
        Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
        MutablePropertySources sources = ((ConfigurableEnvironment)environment).getPropertySources();
        PropertySource attached = sources.get("configurationProperties");
        if (attached != null && attached.getSource() != sources) {
            sources.remove("configurationProperties");
            attached = null;
        }

        if (attached == null) {
            sources.addFirst(new ConfigurationPropertySourcesPropertySource("configurationProperties", new SpringConfigurationPropertySources(sources)));
        }

    }

校验原环境配置属性列表中是否包含 configurationProperties 若不包含则新增进去。存在的话对比是否是同一个对象,若不是则删除重新添加。这里比较有意思的是,sources是从环境对象中获取的,然后将获取的结果通过SpringConfigurationPropertySources封装,并作为sources的一个新的configurationProperties属性放入sources中.

到目前为止还不能明确这么做的目的。带着疑问继续向下看。

listeners.environmentPrepared:这个就不多说了,环境已经准备好执行与此相关的监听器。

bindToSpringApplication

这个就比较有意思了,从名字理解,为SpringApplication绑定,绑定什么呢?

    protected void bindToSpringApplication(ConfigurableEnvironment environment) {
        try {
            Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
        } catch (Exception var3) {
            throw new IllegalStateException("Cannot bind to SpringApplication", var3);
        }
    }

为了方便理解binder的原理,我们在放一下binder.get的源码:

    public static Binder get(Environment environment, BindHandler defaultBindHandler) {
        Iterable sources = ConfigurationPropertySources.get(environment);
        PropertySourcesPlaceholdersResolver placeholdersResolver = new PropertySourcesPlaceholdersResolver(environment);
        return new Binder(sources, placeholdersResolver, (ConversionService)null, (Consumer)null, defaultBindHandler);
    }

从这里可以看到一个ConfigurationPropertySources.get方法,从环境中获取了ConfigurationPropertySource的配置资源迭代器。

其源码:

    public static Iterable get(Environment environment) {
        Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
        MutablePropertySources sources = ((ConfigurableEnvironment)environment).getPropertySources();
        ConfigurationPropertySourcesPropertySource attached = (ConfigurationPropertySourcesPropertySource)sources.get("configurationProperties");
        return attached == null ? from((Iterable)sources) : (Iterable)attached.getSource();
    }

这里知道了前文为何又将配置资源设置到configurationProperties中,在这里通过可配置环境类ConfigurableEnvironment获取环境中的指定的配置资源。
这里最后返回的结果是Iterable类型,因为configurationProperties内保存的是一个列表。

我们再回到Binder.get的源码中。除了获取环境类中的配置资源,还初始化了一个配置资源占位符解析器

  PropertySourcesPlaceholdersResolver placeholdersResolver = new PropertySourcesPlaceholdersResolver(environment);

PropertySourcesPlaceholdersResolver:

    public PropertySourcesPlaceholdersResolver(Iterable> sources, PropertyPlaceholderHelper helper) {
        this.sources = sources;
        this.helper = helper != null ? helper : new PropertyPlaceholderHelper("${", "}", ":", true);
    }

这样就比较直观,这里是要替换掉配置文件中的${xx}占位符。

至于bind方法这里不打算展开说了,知道是将spring.main下面的配置绑定到SpringApplication对象上。如:sources,bannerMode等属性赋值给当前的对象。

这里额外提一点,从类的名称区猜测类的作用在spring源码中真的非常好用,比如上面源码中的ConfigurableEnvironment,从名称可猜测为可配置的环境,他的实现类之一就是我们上面说的StandardServletEnvironment,spring将环境中可配置属性的方式,提到接口中。既方便扩展,也直观的展示了那些属性是可以通过配置修改的。

判断是否需要转换环境对象

        if (!this.isCustomEnvironment) {
            environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
        }

isCustomEnvironment参数默认为false,这里必定会执行。内部对比了当前环境对象是否为StandardEnvironment子类。若不是则重新生成StandardEnvironment环境对象,并将内部的参数转换到新的对象中

方法结尾,重新执行了ConfigurationPropertySources.attach方法。因为前文可能已经对资源或环境类进行乐修改。这里相当于重新初始化了一次环境配置资源类中的configurationProperties的配置。

到此为止,我们讲完了prepareEnvironment(环境对象初始化流程)。

configureIgnoreBeanInfo :设置需要被忽略的beaninfo。

最后介绍一下:printBanner 启动banner的打印。

源码:

    private Banner printBanner(ConfigurableEnvironment environment) {
        if (this.bannerMode == Mode.OFF) {
            return null;
        } else {
            ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader : new DefaultResourceLoader((ClassLoader)null);
            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);
        }
    }

this.bannerMode在SpringApplication构造函数内初始化(部分版本源码是创建时初始化)。默认为“CONSOLE”。
上面的源码比较简单,提供了两种打印方式,并初始化一个banner对象。
bannerPrinter.print内提供了两种不同的banner类型。

    private Banner getBanner(Environment environment) {
        SpringApplicationBannerPrinter.Banners banners = new SpringApplicationBannerPrinter.Banners();
        banners.addIfNotNull(this.getImageBanner(environment));
        banners.addIfNotNull(this.getTextBanner(environment));
        if (banners.hasAtLeastOneBanner()) {
            return banners;
        } else {
            return this.fallbackBanner != null ? this.fallbackBanner : DEFAULT_BANNER;
        }
    }

getImageBanner和getTextBanner,图片和文本两种模式。
这里还未真正打印,只是初始化好了一个banner对象。

    private Banner getTextBanner(Environment environment) {
        // spring.banner.location 属性, 默认为 banner.txt
        String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
        Resource resource = this.resourceLoader.getResource(location);
        try {
            if (resource.exists() && !resource.getURL().toExternalForm().contains("liquibase-core")) {
                return new ResourceBanner(resource);
            }
        }
        catch (IOException ex) {
            // Ignore
        }
        return null;
    }

    private Banner getImageBanner(Environment environment) {
        //spring.banner.location 需要指定文件
        String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY);
        if (StringUtils.hasLength(location)) {
            Resource resource = this.resourceLoader.getResource(location);
            return resource.exists() ? new ImageBanner(resource) : null;
        }
        for (String ext : IMAGE_EXTENSION) {
            Resource resource = this.resourceLoader.getResource("banner." + ext);
            if (resource.exists()) {
                return new ImageBanner(resource);
            }
        }
        return null;
    }

通过配置可以看出在resource下默认创建一个banner.txt文本即可替换原始的banner信息。这种方式比较简单。

  • 4 小结

本文讲解了从启动到创建上下文之前的动作,包括SpringApplication初始化(加载spring.factories文件中的配置类,监听器等),运行环境的初始化(环境类以及内部的配置资源的初始化),banner对象的构建等。

到此为止都算比较简单,这里重点关注一下环境类(Environment)后续使用的会比较多,配置资源是从这里获取,提供了springboot运行时环境。

文章讲的比较啰嗦,很多点没有详细深入,如果详细的说,一篇文章根本说不完,比如监听器以及一些工具类等。

简单的流程:
1.初始化SpringApplication构造函数
2.从spring.factories文件中加载配置类
3.加载配置资源(系统配置,环境变量,项目的application.yml配置等)
4.初始化环境参数,将配置资源注入
5.构建banner对象

你可能感兴趣的:(springboot启动流程1--启动前的准备)