所谓属性源,其实就是一个属性集合,它内部封装了多个name/value键值对,通过name可以获取与之对应的value值。
PropertySource属性源对象通常不单独使用,而是通过一个PropertySources(注意s)对象,我称它为属性源集合对象,由这个对象来统一管理。PropertySources其实就相当于一个Collection容器,其内部聚集了多个PropertySource属性源对象,且有序。它可以按序遍历内部持有的每个属性源,搜索name对应的value,找到即返回。
PropertySources属性源集合又跟PropertyResolver属性解决器协作,共同解决${}格式的属性占位符。
最后,总结一下PropertySource、PropertySources和PropertyResolver三者之间的关系:
属性解决器可以处理嵌套结构的占位符,而占位符对应的的值来自于PropertySources属性源集合,PropertySources负责搜索内部的每个PropertySource(它才是属性值的真正保存者)。
属性源体系图如下:
除以上体系之外,PropertySource还有2个内部类实现:
StubPropertySource:占位属性源。这是一个只有名称标识,没有任何值的属性源,只用作占位符,当实际的属性源对象不能在ApplicationContext应用上下文创建的时候被立即初始化,则会使用它来占位,以保证属性源集合的搜索顺序。举个例子:在Web环境中,创建StandardServletEnvironment对象时,会先调用customPropertySources追加ServletConfig和ServletContext两个属性源对象,但是此时并没有这两个对象的引用(两个对象的引用通过initPropertySources初始化时传入),因此会创建StubPropertySource对象占位,当初始化时再用实际属性源替换掉(根据name匹配)占位对象。
ComparisonPropertySource:比较属性源,继承自StubPropertySource。它只用作根据name获取属性源对象时比较。MutablePropertySources.get(String name)提供了根据名称获取属性源的接口,但是List接口只有通过索引获取元素(get),或者通过元素获取索引(indexOf)的接口。因此会先创建ComparisonPropertySource对象,indexOf通过equals比较获取属性源索引,再get获取真实的属性源。
public PropertySource get(String name) { int index = this.propertySourceList.indexOf(PropertySource.named(name)); return (index != -1 ? this.propertySourceList.get(index) : null);}
体系详解:
PropertySource<T>:
所有属性源的抽象基类,内部包含2个属性:name名称为属性源对象的唯一标识;source源对象封装着name/value属性对。具体封装方式由子类实现决定,泛型T指定了源对象的具体类型。该类定义了组件的核心功能:判断name属性是否存在;根据name获取对应的value。另外,上文提到的两个内部类也在当前基类中定义。通过一个静态的named(name)方法,可以创建一个用于比较的ComparisonPropertySource对象。EnumerablePropertySource<T>:继承自PropertySource<T>。
可枚举属性的属性源,额外定义了获取所有属性名称的方法public abstract String[] getPropertyNames();
MapPropertySource:继承自EnumerablePropertySource<T>。
以Map<String, Object>对象作为源对象封装属性。这是一个直接可用的完整实现类。PropertiesPropertySource:继承自MapPropertySource。
以Properties(extends Hashtable<Object,Object>)对象作为源对象封装属性。SystemEnvironmentPropertySource:继承自MapPropertySource。
系统环境属性源,此属性源在根据name获取对应的value时,与父类实现不太一样。它认为name不区分大小写,且name中包含的'.'点与'_'下划线是等效的,因此在获取value之前,都会对name进行一次处理。@Override public Object getProperty(String name) { // 解决属性名 String actualName = resolvePropertyName(name); if (logger.isDebugEnabled() && !name.equals(actualName)) { logger.debug(String.format("PropertySource [%s] does not contain '%s', but found equivalent '%s'", getName(), name, actualName)); } return super.getProperty(actualName); } private String resolvePropertyName(String name) { Assert.notNull(name, "Property name must not be null"); // 如果source对象中存在此name,则直接返回 if (containsKey(name)) { return name; } // source对象中不存在此name,则把name中的'.'替换成'_',再次尝试 String usName = name.replace('.', '_'); // 如果source对象中存在替换后的name,则返回替换后的name if (!name.equals(usName) && containsKey(usName)) { return usName; } // 还是没有则再次尝试原始name转大写 String ucName = name.toUpperCase(); if (!name.equals(ucName)) { if (containsKey(ucName)) { return ucName; } // 还是不存在,则转大写后,再'.'替换成'_'尝试 else { String usUcName = ucName.replace('.', '_'); if (!ucName.equals(usUcName) && containsKey(usUcName)) { return usUcName; } } } // 都不存在,则返回原始name return name; } private boolean containsKey(String name) { return (isSecurityManagerPresent() ? this.source.keySet().contains(name) : this.source.containsKey(name)); } protected boolean isSecurityManagerPresent() { return (System.getSecurityManager() != null); }
CommandLinePropertySource:继承自EnumerablePropertySource<T>。
以输入命令行参数作为属性源的对象。命令行参数就是main方法传入的String[]数组值,在命令行中输入的字符串默认会以空格为分隔符被拆分成String数组。如:java -dkey=value Test.class --name1=value1 --name2=value2 abc
"-dkey=value Test.class"代表虚拟机参数,而"--name1=value1 --name2=value2 abc"会以空格拆分传入main方法。
CommandLinePropertySource属性源内部分为选项参数和非选项参数,选项参数带有特定的前缀(一般为"--"),非选项参数则相反。命令行参数语法一般由解析器定义,如:
SimpleCommandLineArgsParser定义:选项参数的前缀为"--"。当然,我们也可以自定义解析器。
注意:CommandLinePropertySource属性源本身只负责属性的存取,是不负责对命令行参数的解析的。
SimpleCommandLinePropertySource:继承自CommandLinePropertySource<CommandLineArgs>。
简单命令行属性源,使用SimpleCommandLineArgsParser解析器对象解析输入的String数组,把返回的CommandLineArgs对象作为属性的来源。JOptCommandLinePropertySource:
基于JOpt Simple的属性源实现,JOpt Simple是一个解析命令行选项参数的第三方库。坐标如下:net.sf.jopt-simple jopt-simple 5.0.1 CompositePropertySource:继承自EnumerablePropertySource<T>。
合成属性源,内部持有一个Set<PropertySource<?>>属性源集合。因为每个属性源对象都有一个名称标识,那么当多个属性源对象共享同一个名称时,就需要CompositePropertySource对象把相同名称的所有属性源对象聚合到一起。