一步一步分析SpringBoot启动源码(三)

分享 未结 精帖 3 15689
KSE-music
KSE-music LV4 2018-07-17
悬赏:20积分
一、 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);这句代码依旧是见其名而知其义,简单说就是包装并解析Spring应用通过main方法传过来的args,而在DefaultApplicationArguments的构造函数内部又用args构造了一个Source(属性源,确切它是一个命令行属性源),Source的继承图如下:

接下来看它如何解析命令行参数的:
通过一步一步debug不难发现在Source的构造器中调用了父构造器即SimpleCommandLinePropertySource的构造器,而在其构造器中又调用了它的父构造器CommandLinePropertySource,最后一步步调用到类图的顶层PropertySource,而它的构造器接收两个参数,一个是属性源名称,一个是属性源,在这里就是我们通过main传进来的命令行参数。这是一个完整过程,而具体解析命令行参数是发生在SimpleCommandLinePropertySource的构造器中,在它的构造器由于它 父构造器接收的是属性源,所以不得不给它一个属性源,在这里就是CommandLineArgs(同包可见),通过构建一个SimpleCommandLineArgsParser(同包可见)对象,里面只有一个parse方法,接收可变长参数,如下图:


解析的逻辑很简单,就不细说了。该方法结束就返回了一个CommandLineArgs对象,正好SimpleCommandLinePropertySource的父构造器就需要这个,因为我们没给这个属性源一个名字,它就使用了默认的命令行属性源名称即commandLineArgs,最终,就得到一个属性源,它的名字是commandLineArgs,值就是parse返回的CommandLineArgs对象,因为我这里没有加命令行参数,所以最后得到的 optionArgs 的size = 0。

二、ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);继续见其名知其义,即开始为我们在启动Spring应用准备环境了,大家都知道Spring是一个高扩展低耦合的框架,所以这里返回的可配置环境,言外之意就是告诉我们,此时准备的环境是在后续的过程是可以干预的即可修改一些默认的环境变量,包括后面即将讲解的Spring应用上下文的创建,它得到的也是一个可配置的应用上下文。
一句话:Spring是一个在它启动的任何阶段都可以让用户去干预它。
1、私有方法prepareEnvironment接收的一个参数是SpringApplicationRunListeners和刚刚解析的ApplicationArguments,很显然这么做的目的有二:一就是在创建完环境后把之前解析的属性源也添加进去,二就是在环境完全准备完后,发送SpringBoot应用启动过程第二个事件ApplicationEnvironmentPreparedEvent,即告诉那些监听器,环境现在准备好了,你们可以干一些事了,过程和starting一样。

2、在prepareEnvironment方法里第一句就是调用私有方法getOrCreateEnvironment,看到方法名,我就特别想说:MD名字取得好,真好!里面的逻辑很简单:首先判断我们在构建boot应用之前有没有自定义环境,有就直接返回,没有,再判断当前是不是web环境,是则创建一个标准的servlet环境,不是则创建一个标准环境。这里我是web应用,所以返回的标准的servlet环境。此时环境里已经有四个属性源了,如下图所示:


那么这四个属性源是怎么来的?我们从StandardServletEnvironmen的类图看,结构如下:

基础知识:子类是默认调用父类的构造器的。所以经过两层调用就到了AbstractEnvironment中的构造器,而里面干两件事,一个就是调用customizePropertySources方法,第二个判断是否看起debug日志打印。在AbstractEnvironment中发现customizePropertySources是个被protected修饰的方法,表名这个就是等着子类去覆写呢,对照继承图,不难发现,此时实质调用的是StandardServletEnvironment中的customizePropertySources方法,在这里上来就添加了两个属性源,一个就是servlet上下文初始化参数,一个是servlet配置参数,就是当年在web.xml配置servlet时候用到的init-param标签,然后检查是否有默认的jndi环境可用,有则添加一个jndi属性源,这里没有。最后又调用了它的父类的customizePropertySources,即StandardEnvironment的customizePropertySources,这里又添加两个属性源,一个是systemProperties(System.getProperties()),另一个systemEnvironment(System.getenv())。总共四个。

科普:
Java提供了System类的静态方法getenv()和getProperty()用于返回系统相关的变量与属性,getenv方法返回的变量大多于系统相关,getProperty方法返回的变量大多与java程
序有关。
3、configureEnvironment(environment, applicationArguments.getSourceArgs());这里就是前面说的prepareEnvironment方法的第一个目的:把命令行参数加到环境中。configureEnvironment是个模板方法,里面执行了configurePropertySources和configureProfiles方法。
configurePropertySources方法逻辑看下图:

属性源添加的位置特别注意了,最前面的优先权最高,即下标为0 的,此时从图中也就可以看出为什么在通过java -jar启动应用时,命令行的属性可以覆盖我们在配置文件中定义的,不管你使用的是application.properties还是自定义的,因为它被添加到第一个位置了。
4、configureProfiles就是配置以何种模式启动的应用,如果在提供了附加的配置文件,此时就会把它们一并添加到环境。比如开发、测试、生产启动,但是一般我们都是以一种模式启动即要么是测试环境要么生产环境。

5、接下来就是发布环境准备好的事件(ApplicationEnvironmentPreparedEvent)了,经过过滤发现还剩下不少,如下图所示:


这里我们按事先的约定只讲第一个,即ConfigFileApplicationListener,这里主要干的就是解析应用配置文件,就是我们经常写的application.properties或者application.yml。这个类不但实现了应用监听器而且实现了EnvironmentPostProcessor这是个环境后置处理器,意味着它要在环境准备好要干一番大事了。在它的onApplicationEvent方法中首先还是判断当前是什么事件,这里是ApplicationEnvironmentPreparedEvent所以就进入onApplicationEnvironmentPreparedEvent方法,在这个方法里,首先是加载环境后置处理器,手段和之前获取监听器以及初始化器一样。同时又把自身加进去,因为它也是实现了EnvironmentPostProcessor,再进过排序,结果如下:



当然我们这里只讲ConfigFileApplicationListener,所以直接进入postProcessEnvironment方法,里面调用三个方法,分别是
addPropertySources: 单开篇幅
configureIgnoreBeanInfo: Skip search of BeanInfo classes.默认true
bindToSpringApplication:单开篇幅

6、最后是判断当前环境是不是web环境,不是的话,则需要把其转换成标准的环境,即去掉web环境相关的属性源,就是前面那两个servlet打头的属性源。
回帖