这篇文章接着 springboot2.x初探(二)
下面看第九步,准备运行环境
上面第八步准备好的参数在这里用上了,看看这个方法:
先看获取环境的方法:
通过debug可以知道我这里的应用是 SERVLET类型,所以创建的是 StandardServletEnvironment。
然后看配置环境的方法:
可以看到它就是一个模版方法,是为了有顺序地调用configurePropertySources方法和configProfiles方法。
这个方法的第一行代码就带来了疑问,这个environment了里面的propertysources是从哪里来的?
我们再回过头来看看获取环境的地方:
看看这个new的过程中都经历了什么:
在这个StandardServletEnvironment类中没有发现无参构造函数,它会去调用父类StandardEnvironment的无参构造函数,可是在StandardEnvironment中也没有无参构造函数,继续向上找,发现它的父类AbstractEnvironment中有一个无参构造函数:
这个构造函数中调用了一个customizePropertySources方法,这个方法在StandardServletEnvironment类中被重写了:
它还调用了父类的这个方法:
其中的addLast方法:
就是设置propertySource列表
到这里也就知道了这些propertySources其实在创建环境的时候就已经设置好了,所以这里
直接获取没啥毛病。
这里看一下sources都有啥:
有四个,两个servletConfigInitParams,两个systemEnvironment
我们看看这两个systemEnvironment都有啥:
看到这些信息我真是瑟瑟发抖~~~
所有我电脑的信息,配置的环境变量,jvm的信息,jdk的信息一览无余!
接着看configureProfiles方法吧:
程序运行到这里,经过debug验证,因为我在application.properties中没有配置 spring.profiles.active,所以获取到的activeProfiles是空的。
下面继续看配置监听器的环境:
可以看到实际上调用了listener自己的environmentPrepared方法,
这个方法中的代码是不是很眼熟?
原来它跟starting方法中的调用特别像
只不过广播的事件类型从startingEvent改为了environmentpreparedEvent。我们看到ApplicationEnvironmentPreparedEvent构造函数中传入了三个参数,除了application,args之外还多了environment,我们看看这个构造函数:
显式调用了父类的构造函数:
又显式调用了父类的构造函数:
继续看:
像这样每个字类都多个字段的设计是什么设计模式来着?(下面再看吧,这里先继续往下看)
接下来调用multicastEvent方法的过程就和starting方法时是一样样的了,这里就不看了。
看到这里我们就知道这个 listeners.environmentPrepared(environment);方法实际上是对preparedEvent事件的广播。
下面继续看bindToSpringApplication(environment);
这个Binder是什么呢?看源码和注释
它就是一个容器类,用来绑定多个对象。在这个类加载的时候,有个静态代码块同时执行了,它添加了一个 JavaBeanBinder,然后将他放到一个list中并赋值给了一个不可变的list属性。
接下来看它的get方法:
从注释看它是从给定的环境中创建一个Binder,其中的两个参数调用了两个方法,我们先看第一个方法:
它返回了一个先前附加到environment上的 ConfigurationPropertySource实例集合,我们来逐行分析代码:
这行代码返回了之前创建环境的时候设置的propertySources:
返回一个为了进行比较的PropertySource接口实现类实例。注释中还给出了用法,正是跟我们现在分析的代码一样的。
我们看这个ComparisonPropertySource就是专门用来做比较用的。它是一个内部类,并且继承了StubPropertySource,这个StubPropertySource类又是做什么的呢?
从它的注释知道它就是一个占位符,是一个当应用上下文创建过程中一个实际的property source的占位符。
上面我们看到named方法中调用了ComparisonPropertySource的构造函数,然后它又调用了StubPropertySource类的构造函数,从上面的截图中我们可以看到StubPropertySource的构造函数也就只是调用了它父类的构造函数:
没有什么其他的操作。
所以这个方法:
实际就是获取configurationProperties的PropertySource。然后做了强制转换:
然后判断获取到的值是否为null,若为空则调用from方法,若不为空则返回它的source。
我们看看from方法:
从上面的代码截图可以发现就是用了一个适配器将 MutablePropertySources转换为了ConfigurationPropertySource。
下面看第二个参数:
可以看到它就是一个占位符解析器。
这个方法是获取环境中的PropertySources。
这个有两个参数的构造函数中,第一个参数通过上面截图中的getSources方法获取,另一个参数为null,从构造函数总看到当helper为空的时候new了一个,这个PropertyPlaceholderHelper是什么呢?
处理有占位符的字符串的公共类。
可以看到在加载PropertyPlaceholderHelper的时候会在wellKnownSimplePrefixes中添加三种常见的 前后缀,然后在构造函数中会对传入的后缀进行判断,下面继续看Binder的构造函数:
从下面这一句可以知道调用了ApplicationConversionService.getSharedInstance方法:
从上面这个方法可以知道是用单例模式创建了一个ApplicationConversionService的实例。
我们看到这个方法的参数是FormatterRegistry类型,但是上面传递的却是 this关键字,我们看看这个类是否继承或实现了FormatterRegistry:
果然它的父类实现了这个接口类,在跟代码的过程中我们发现ApplicationConversionService类的两个父类都没有显式地实现无参构造函数,所以我们只看当前类的构造函数就可以。
先看看第一个 addScalaConverters方法:
这个方法中给ApplicationConversionService类添加了这么多转换器。我们看第一个:
下面的 1处我们已经看过了,我们看 2 处的代码
这里使用的是递归调用,最终返回一个ResolvableType,接着看方法中的第二行代码:
返回一个代表一般的参数的ResolvableType数组。具体的方法内容这里就不做分析了,下面有时间了再仔细看。
所以
方法中的typeInfo就是代表一般的参数的ResolvableType数组。
其他的增加转换器的过程就不再看了。下面继续看
上面是增加的转换器,下面的是格式注册:
然后是:
上面这一块就先到这吧,先不进行更深度的分析了,我们继续往下走上面看了那么多,其实我们才看到这里:
还有这里:
接下来我们看这里:
具体的bind,前面的get部分我们基本上看完了,下面看bind部分:
两个参数:name和Bindable的泛型,我们上面传递的是什么参数呢?
一个是"spring.main",一个是一个方法,我们看看后面第二个参数:
用一个等于现有实例的值创建一个指定实例的新的可绑定项。(我也不知道这是什么意思,只是翻译注释,等后面再仔细理解。)
下面继续看bind方法:
由于上面传入的handler是null所以这里会调用BindHandler.DEFAULT,我们看看这个:
同时在这个接口中我还发现了一些方法实体!!!
在我原来的认知中,接口中是不能 有方法实体的,那这里为什么可以这么写呢?
原来从jdk1.8之后接口中也可以有方法实体了,这样似乎更符合面向对象特性,不过方法前要加上 default修饰。这样的方法是可以直接在子类中调用的。
到这里我们先看到BindResult.of(bound)。其他的详细细节先不看了,内容太多了,并且现在没什么心情看了。
bindToSpringApplication方法我们就先看到这里,接着看下面的判断代码块。
对于这里的判断,我们已经知道我们的应用是SERVLET类型的所以不会走这块代码,直接往下看attach方法:
这段代码的主要作用就是将 name为 configurationProperties的PropertySource放到列表中第一个。
到这里run方法中的第九步就基本看完了,当然还有很多细节没有看,那些不是现在这篇文章要分析的,我们先继续看第十步:
我们看具体的方法实现:
经过分析可以看到,这个方法就是将系统属性中的 spring.beaninfo.ignore设置为true。它的作用是跳过搜索BeanInfo类。具体究竟是怎么起作用的呢,我还不知道,后面遇到再分析。
后面内容还多,将在下面的文章中继续分析。