前言:
springboot的框架太复杂了,如何学习springboot才能不让自己迷失在细节里,其实从设计模式学习就很好,因为设计模式是思想,思想引导行为,只有理解了思想,才能看得懂springboot的行为。
正文:
springboot的环境准备阶段分别从jvm启动参数、启动指令、系统环境变量,硬编码指定、配置文件等方式,配置各种参数,并装配到Environment的propertySources资源集合里。
propertySources对应的对象是 MutablePropertySources,而 MutablePropertySources对象是采用java的23种设计模式里的迭代器模式,它实现了Iterable<PropertySource<?>>的接口,并通过内部持有PropertySource<?>的集合,来实现PropertySource<?>迭代访问。
PropertySource<?>(看清楚,是PropertySource<?>,而不是PropertySources。)是一种特殊的对象,该对象,包装了一个资源(Object类型),并为Object起了一个名字。Springboot的资源一共有:jvm启动指令,jvm启动参数、 系统环境变量、编码指定变量、配置文件变量等,这里的每一个都可以归类为一种资源(这种资源还可以再分,再细小归类为一类资源),并且每个资源都可以给他们 起一个名字,将着两者信息封装起来就是一个PropertySource<?>对象。
springboot的PropertySource类型有很多种,且这些集合的信息有可能有部分值是重复的,为了方便管理这些资源,springboot又提供了一个PropertySource集合---PropertySources(注意多了一个s),该集合采用迭代器设计模式,对PropertySource资源进行统一管理。
但这里会有两个让人迷惑的地方:
第一个就是PropertySource的一个实现类CompositePropertySource和PropertySources的区别,CompositePropertySource使用的是组合设计者模式,它的出现主要是为了解决,多个资源拥有同一个名称的问题。
什么叫多个资源拥有同一个名称呢?举例:我们都知道PropertySource是一个name对应一个资源A,但是有时候,我们在不同的场景下,使用了资源A的子集去创建了一个PropertySource,后面我们想要将这些子集再整合成一个A的时候,就可以用CompositePropertySource进行整合了,这就是CompositePropertySource的作用。而PropertySources则是整合多个名称不同的资源。
第二个就是PropertySource是一个抽象类型的对象,而且他没有顶级接口。什么情况下会出现这样的情况呢?其实就是在桥接模式下才会出现这样的情况。
PropertySources体系
PropertySources体系比较简单,它有三个实现类,一个基础类,还有两外两个分别在基础类上做了组合,基础类MutablePropertySources,这是PropertySources的集合,它通过内部持有线程安全的CopyOnWriteArrayList集合,对PropertySources进行有序整合(有序整合的目的,是因为不同的资源类型,它的属性有重叠,通过有序组合,可以做到优先级控制的作用)。
而PropertySources的另外两个子类FilteredPropertySources和CompositePropertySources,则分别采用了装饰者模式、组合模式,对PropertySources进行了改造,FilteredPropertySources通过装饰者模式,对PropertySources进行了拦截。CompositePropertySources则是前面已经进行了阐述。
PropertySource体系
PropertySource则是一个重头戏,我们都知道它采用的是桥接模式,将自己本身作为桥,将资源与他的名称进行连接起来,它的泛型就是资源类型,而PropertySource的诸多子类,则表示者,其本身桥接的资源。
AnsiPropertySource:桥接的是名为Ansi的资源,而Ansi指的是字符编码、颜色、背景等资源的封装,他有什么用呢?我们都知道springboot启动时,控制台会打印日志,这些日志的颜色、背景色等设置,就是通过这个资源进行获取的。
ConfigurationPropertySourcesPropertySource:很明显,它桥接的是ConfigurationPropertySources类型的资源,ConfigurationPropertySources是什么类型?就是@ConfigurationProperties注解底层使用的封装,获取配置文件中的prefix,和注解对象的类成员变量,递归将配置属性赋值给类成员变量这一类型的资源。
JndiPropertySource:支持java的JNDI类型的资源
RandomValuePropertySource:封装的是Random类型的资源,随机资源。
StubPropertySource:占坑资源,该资源可以进行替换的,比如servletConfigI类型参数、servletContext的类型资源,在spring准备环境阶段,还没有生成,但是没关系,先用这个资源占个坑位,后面再进行替换,这样做的好处,还是为了能够对资源进行优先级排序。
EnumerablePropertySource:枚举类型的资源,啥叫枚举类型的资源,其实就是对于资源的类型,我们基本上已知的,比如spring的注解,有哪些注解,我们大体上是已知的。
EnumerablePropertySource类型的子类:
AnnotationsPropertySource:注解类型的资源,该资源通过获取对应的注解,然后迭代获取其注解
CommandLinePropertySource:指令类型的资源,对java的指令参数进行解析
CompositePropertySource:使用的组合模式,将Properties进行组合,前面已经有所介绍。
MapPropertySource:map集合类型的资源。
ServletConfigPropertySource:ServletConfig类型的资源
ServletContextPropertySource:ServletContext类型的资源
分析完了资源的结构,接下来就分析环境装配问题。
在这个关系图中,左边的是负责环境创建的,右边的部分是负责解析的。其实环境的装配和使用,使用的就是装饰者模式。
首先,环境内部持有一个解析器,即右边的PropertySourcesPropertyResolver,然后他还持有一个资源集合,就是上面的PropertySources,并将资源传递给PropertySourcesPropertyResolver去解析。
环境的创建有两种类型StandardEnvironment和StandardServletEnvironment,两者的区别是前者是不支持web环境,后者支持web环境,由于我们一般情况下都是使用的web环境,所以将对web的环境流程进行说明。
StandardEnvironment初始化,虽然看起来简单,但其实里面埋了一个雷:
JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()会触发JndiLocatorDelegate对象里有一个静态变量会被初始化,这个静态变量就是private static final boolean shouldIgnoreDefaultJndiEnvironment =SpringProperties.getFlag(IGNORE_JNDI_PROPERTY_NAME);他会触发SpringProperties程序加载classpath路径下的spring.properties,并从中获取spring.jndi.ignore属性,如果spring.properties里获取不到,则从系统获取,他会引发jndi一些的程序。
在这个过程中,他会加载以下资源(并不是按照如下顺序加载的),并将加载的资源类型按照如下进行排序。
bindToSpringApplication(environment)
这里还有一个需要讲解的时bindToSpringApplication(environment)方法,该方法是将springboot1.0的环境属性,同时复制到2.0提供的新属性上来。所谓的springboot2.0提供的新属性,指的就是提供了一个新的属性集合,ConfigurationPropertySource。提供该集合的原因,是为了可以更好的解析一些新资源的属性,比如集合类型的解析。
此方法主要功能时使用ConfigurationPropertySources这个适配器将原来1.0的资源集合适配到2.0的资源集合即SpringConfigurationPropertySources上。同时,还将使用Binder对象,将属性解析器与转换后的2.0资源集合SpringConfigurationPropertySources进行一个绑定,方便后期处理。
另外,还需要说明的时,环境准备发布的时候,会通知相关监听,这些监听里有一个重要的监听,那就是ConfigFileApplicationListener,该监听监听换进准备完成后,将会向环境里加入random类型的资源即RandomValuePropertySource,以及一个applicationConfig的,值为[classpath:/application.properties]资源类型