Spring之PropertyPlaceholderConfigurer加载配置文件

1.是什么

PropertyPlaceholderConfigurer是spring中bean的后置处理器的实现类,也就是BeanFactoryPostProcessor接口的一个实现类

2.工作场景

  1. 工程中经常会把配置项放在一个或者多个Properties文件中,项目启动时加载Properties文件到Properties中,然后进行替换Spring容器中BeanDefinition对应属性中${}
  2. 当我们配置项中会涉及到数据库密码之类的敏感数据,我们需要加密放置在配置文件中,该场景可以继承PropertyPlaceholderConfigurer方法重写convertProperty方法,在Spring启动加载配置文件到Properties后进行解密,然后进行替换Spring容器中BeanDefinition对应属性中${}

3.使用方法

工作场景1的使用方法

工作场景1的方法比较简单,流程如下,以数据配置文件为例:

  1. 编写jdbc.properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/data?serverTimezone=UTC
jdbc.username=root
jdbc.password=123456
  1. 配置PropertyPlaceholderConfigurer加载文件
    在Spring配置文件applicationContext.xml中配置,随Spring启动进行加载
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="ignoreResourceNotFound" value="true" />
        <property name="locations">
            <list>
                <value>classpath:config/jdbc.properties</value>
            </list>
        </property>
    </bean>

如果引用多个文件有两种形式
形式1

    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="ignoreResourceNotFound" value="true" />
        <property name="locations">
            <list>
                <value>classpath:config/*.properties</value>
            </list>
        </property>
    </bean>

形式2

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
	<property name="locations">
		 <list>  
			<value>classpath:config/jdbc.properties</value>
			<value>classpath:conf.properties</value>
		 </list>
	</property>
</bean>
  1. 配置项使用场景有两种使用方法
    1)配置文件中使用
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

2)在代码中通过@Value使用,例如

    @Value("${jdbc.username}")
    private String username;

工作场景2的使用方法

当文件中存在加密配置时,这些配置项并不能直接使用,需要进行解密后才能使用

  1. 编写jdbc.properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/data?serverTimezone=UTC
jdbc.username=root
jdbc.password=mJephukdo4Js82iZSmCu25mSBSO+J5Rs0li8taFtbCs=

2.自定义ReadPropertyCfg类继承PropertyPlaceholderConfigurer,重写convertProperty方法

public class ReadPropertyCfg extends PropertyPlaceholderConfigurer implements InitializingBean {
    private static Map<String, String> ctxPropertiesMap =
        new HashMap<String, String>(16);
    /**
     * 需要做解密处理的key
     */
    private static Set<String> encryptSet = new HashSet<String>(16);
    static {
        // 需要做加解密的key
        encryptSet.add("jdbc.password");
    }
    /**
     * 配置文件中加密配置属性的转换
     *
     * @param propertyName  配置的名称
     * @param propertyValue 配置的值
     * @return 转化结果
     */
    @Override
    protected String convertProperty(String propertyName, String propertyValue) {
        if (null != propertyName && (encryptSet.contains(propertyName))) {
            String pwdresult = AESCryptor.decryptData(propertyValue, CipherConfig.getWorkCipherKey("mysql.workkey"));
            return pwdresult;
        }
        return propertyValue;
    }
    /**
     * 在初始化根秘钥
     *
     * @throws IOException
     * @throws DecoderException
     */
    @Override
    public void afterPropertiesSet() throws SecureUtilCommonException, DecoderException, IOException {
        setFileEncoding("UTF-8");
        CipherConfig.init();
    }
}

对上述代码进行简要分析,encryptSet是放置所有需要解密的属性,convertProperty方法把需要解密的数据进行解密,如何加载到Spring容器中等下分析
3. 在Spring配置文件applicationContext.xml中配置ReadPropertyCfg来加载配置文件

    <bean id="propertyConfigurer" class="com.huawei.alliance.commons.config.ReadPropertyCfg">
        <property name="ignoreResourceNotFound" value="true" />
        <property name="locations">
            <list>
                <value>classpath:config/*.properties</value>
            </list>
        </property>
    </bean>
  1. 配置项使用场景有两种使用方法
    1)配置文件中使用
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

2)在代码中通过@Value使用,例如

    @Value("${jdbc.password}")
    private String password;

注:spring容器中最多只能定义一个context:property-placeholder,否则会报错:Could not resolve placeholder XXX,但如果想引入多个属性文件怎么办那,可以使用通配符:<context:property-placeholder location=“classpath:*.properties”/>

代码中获取配置属性

1.需要自定义ReadPropertyCfg类继承PropertyPlaceholderConfigurer,重写processProperties方法,通过map来进行承接配置项

public class ReadPropertyCfg extends PropertyPlaceholderConfigurer implements InitializingBean {
    private static Map<String, String> ctxPropertiesMap =
        new HashMap<String, String>(16);
   /**
     * 通过重写processProperties方法把Properties属性写入到Map中,然后供后续使用
     */
    @Override
    protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
        throws BeansException {
        super.processProperties(beanFactoryToProcess, props);
        String keyStr = "";
        String value = "";
        for (Object key : props.keySet()) {
            keyStr = key.toString();
            if (null != keyStr && null != props.getProperty(keyStr)) {
                value = props.getProperty(keyStr);
                ctxPropertiesMap.put(keyStr, value);
            }
        }
    }
    /**
     * 通过获取map中的属性的key获取对应的value
     */
    public static String getContextProperty(String name) {
        return ctxPropertiesMap.get(name);
    }
}

4.源码分析

那现在疑问就来了,我们自定义PropertyPlaceholderConfigurer或者直接使用PropertyPlaceholderConfigurer,Spring是如何加载配置项的哪?
我们通过自定义的ReadPropertyCfg 反向进行分析Properties加载过程

从反向可以推导出最终是AbstractApplicationContext中的refresh触发的,具体的在这不进行黏贴,可根据上述调用关系在Spring源码中进行查看即可。
下面对重要的代码进行重点分析

PropertyResourceConfigurer.java
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		try {
			//1)加载配置文件到Properties中
			Properties mergedProps = mergeProperties();

			//2)转化Properties进行重新提交,重写该方法可以对配置项进行处理,例如解密
			convertProperties(mergedProps);

			//3)把Properties加载到spring容器中替换BeanDefinition中${}对应的属性
			processProperties(beanFactory, mergedProps);
		}
		catch (IOException ex) {
			throw new BeanInitializationException("Could not load properties", ex);
		}
	}

由上述代码可知配置文件从加载到最终加载Spring容器中,可分为三步

1)加载配置文件到Properties中

	/**
	 * Return a merged Properties instance containing both the
	 * loaded properties and properties set on this FactoryBean.
	 */
	protected Properties mergeProperties() throws IOException {
		Properties result = new Properties();

		if (this.localOverride) {
			// Load properties from file upfront, to let local properties override.
			loadProperties(result);
		}

		if (this.localProperties != null) {
			for (Properties localProp : this.localProperties) {
				CollectionUtils.mergePropertiesIntoMap(localProp, result);
			}
		}

		if (!this.localOverride) {
			// Load properties from file afterwards, to let those properties override.
			loadProperties(result);
		}

		return result;
	}

2)转化Properties进行重新提交,重写该方法可以对配置项进行处理,例如解密

PropertyResourceConfigurer的convertProperties方法

	protected void convertProperties(Properties props) {
		Enumeration<?> propertyNames = props.propertyNames();
		while (propertyNames.hasMoreElements()) {
			String propertyName = (String) propertyNames.nextElement();
			String propertyValue = props.getProperty(propertyName);
			String convertedValue = convertProperty(propertyName, propertyValue);
			if (!ObjectUtils.nullSafeEquals(propertyValue, convertedValue)) {
				props.setProperty(propertyName, convertedValue);
			}
		}
	}

在上述代码中如果没有重写convertProperty方法,propertyValue不发生变化,还是获取原始配置,源码如下

	protected String convertProperty(String propertyName, String propertyValue) {
		return convertPropertyValue(propertyValue);
	}
	protected String convertPropertyValue(String originalValue) {
		return originalValue;
	}

重写convertProperty方法

    @Override
    protected String convertProperty(String propertyName, String propertyValue) {
        if (null != propertyName && (encryptSet.contains(propertyName))) {
            String pwdresult = AESCryptor.decryptData(propertyValue, CipherConfig.getWorkCipherKey("mysql.workkey"));
            return pwdresult;
        }
        return propertyValue;
    }

在执行convertProperty代码之前Properties已经有了所有的配置项,只不过是配置项中原生的,重写convertProperty就是为了把需要解密的数据进行解析,把需要解密的数据解密后进行重新设置Properties的对应属性达到解密的目的

3)把Properties加载到spring容器中替换BeanDefinition中${}对应的属性

代码或者配置文件已经被加载Spring容器中对应BeanDefinition的属性,只不过现在为止还是以${}的形式存在,processProperties的过程就是把BeanDefinition的对应属性替换为对应Properties中解密后的value

	/**
	 * Visit each bean definition in the given bean factory and attempt to replace ${...} property
	 * placeholders with values from the given properties.
	 */
	@Override
	protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
			throws BeansException {

		StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
		doProcessProperties(beanFactoryToProcess, valueResolver);
	}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于spring配置文件加载properties文件,可以使用以下几种方式: 1. 使用PropertyPlaceholderConfigurer属性占位符 可以在配置文件中使用${}占位符来引用属性值,同时需要在配置文件中引入对应的*.properties文件。在Spring容器启动时,会通过PropertyPlaceholderConfigurer将properties配置文件中的键值对装载到Spring的环境变量中,供${}占位符使用。 例如: ``` <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:/config/app.properties</value> </list> </property> </bean> <bean id="user" class="com.example.User"> <property name="name" value="${user.name}"/> <property name="age" value="${user.age}"/> </bean> ``` 在上面的例子中,将classpath:/config/app.properties中的键值对装载到Spring的环境变量中,供${}占位符使用。 2. 使用util命名空间的PropertiesFactoryBean 可以在Spring配置文件中使用util命名空间的PropertiesFactoryBean来装载properties文件中的属性,并且使用${}占位符引用这些属性值。 例如: ``` <util:properties id="appConfig" location="classpath:/config/app.properties"/> <bean id="user" class="com.example.User"> <property name="name" value="${user.name}" /> <property name="age" value="${user.age}" /> </bean> ``` 在上面的例子中,通过util:properties装载classpath:/config/app.properties中的属性。在User bean中使用${}占位符引用属性值。 希望这些方法能够帮到您!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值