spring-core组件详解——PropertySource属性源

原创
2016/05/09 16:57
阅读数 5.3K

        所谓属性源,其实就是一个属性集合,它内部封装了多个name/value键值对,通过name可以获取与之对应的value值。

        PropertySource属性源对象通常不单独使用,而是通过一个PropertySources(注意s)对象,我称它为属性源集合对象,由这个对象来统一管理。PropertySources其实就相当于一个Collection容器,其内部聚集了多个PropertySource属性源对象,且有序。它可以按序遍历内部持有的每个属性源,搜索name对应的value,找到即返回。

        PropertySources属性源集合又跟PropertyResolver属性解决器协作,共同解决${}格式的属性占位符。

        最后,总结一下PropertySource、PropertySources和PropertyResolver三者之间的关系:

        属性解决器可以处理嵌套结构的占位符,而占位符对应的的值来自于PropertySources属性源集合,PropertySources负责搜索内部的每个PropertySource(它才是属性值的真正保存者)。


属性源体系图如下:

除以上体系之外,PropertySource还有2个内部类实现:

  1. StubPropertySource:占位属性源。
    这是一个只有名称标识,没有任何值的属性源,只用作占位符,当实际的属性源对象不能在ApplicationContext应用上下文创建的时候被立即初始化,则会使用它来占位,以保证属性源集合的搜索顺序。举个例子:在Web环境中,创建StandardServletEnvironment对象时,会先调用customPropertySources追加ServletConfig和ServletContext两个属性源对象,但是此时并没有这两个对象的引用(两个对象的引用通过initPropertySources初始化时传入),因此会创建StubPropertySource对象占位,当初始化时再用实际属性源替换掉(根据name匹配)占位对象。

  2. 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);
}


体系详解:

  1. PropertySource<T>:
    所有属性源的抽象基类,内部包含2个属性:name名称为属性源对象的唯一标识;source源对象封装着name/value属性对。具体封装方式由子类实现决定,泛型T指定了源对象的具体类型。该类定义了组件的核心功能:判断name属性是否存在;根据name获取对应的value。另外,上文提到的两个内部类也在当前基类中定义。通过一个静态的named(name)方法,可以创建一个用于比较的ComparisonPropertySource对象。

  2. EnumerablePropertySource<T>:继承自PropertySource<T>。
    可枚举属性的属性源,额外定义了获取所有属性名称的方法

    public abstract String[] getPropertyNames();
  3. MapPropertySource:继承自EnumerablePropertySource<T>。
    以Map<String, Object>对象作为源对象封装属性。这是一个直接可用的完整实现类。

  4. PropertiesPropertySource:继承自MapPropertySource。
    以Properties(extends Hashtable<Object,Object>)对象作为源对象封装属性。

  5. 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);
    	}
  6. 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属性源本身只负责属性的存取,是不负责对命令行参数的解析的。

  7. SimpleCommandLinePropertySource:继承自CommandLinePropertySource<CommandLineArgs>。
    简单命令行属性源,使用SimpleCommandLineArgsParser解析器对象解析输入的String数组,把返回的CommandLineArgs对象作为属性的来源。

  8. JOptCommandLinePropertySource:
    基于JOpt Simple的属性源实现,JOpt Simple是一个解析命令行选项参数的第三方库。坐标如下:

    <dependency>
    	<groupId>net.sf.jopt-simple</groupId>
    	<artifactId>jopt-simple</artifactId>
    	<version>5.0.1</version>
    </dependency>

  9. CompositePropertySource:继承自EnumerablePropertySource<T>。
    合成属性源,内部持有一个Set<PropertySource<?>>属性源集合。因为每个属性源对象都有一个名称标识,那么当多个属性源对象共享同一个名称时,就需要CompositePropertySource对象把相同名称的所有属性源对象聚合到一起。



展开阅读全文
加载中

作者的其它热门文章

打赏
2
13 收藏
分享
打赏
0 评论
13 收藏
2
分享
返回顶部
顶部