导读
概述
在进行数据源或者邮件服务器等资源配置时,用户可以直接在Spring配置文件中配置用户名、密码、连接信息等,但是有一种更好的方法是将这些配置信息独立到一个外部属性文件中,并在Spring配置文件中通过形如${user}、${password}的占位符引用属性文件中的属性项。
通过这种方式配置拥有两个明显的好处
减少维护的工作量
部署更加简单
Spring提供了一个PropertyPlaceholderConfigurer,它能够使Bean在配置时引用外部属性文件。
PropertyPlaceholderConfigurer实现了BeanFactoryPostProcessorBean接口,因而也是一个Bean工厂后处理器.
PropertyPlaceholderConfigurer属性文件
代码已托管到Github—> https://github.com/yangshangwei/SpringMaster
实例
maven项目
一种不太好的写法
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> <property name="url" value="jdbc:oracle:thin:@172.25.246.11:1521:xgj"/> <property name="username" value="cctb"/> <property name="password" value="xgj2017"/> </bean>
从上面的配置文件中,我们可以看到驱动器类名、JDBC的URL以及数据库的用户名和密码都写在了XML中。部署的时候,如果需要改动数据库的配置信息,需要先找到这个xml,然后修改,不是特别方便。
根据实际应用的最佳实践,我们可以将这些信息抽取到一个配置文件中,取名为 jdbc.priperties (随意取名)
我们先看下jdbc.priperties的配置文件
jdbc.properties
jdbc.driverClassName=oracle.jdbc.driver.OracleDriver jdbc.url=jdbc:oracle:thin:@172.25.246.11:1521:xgj jdbc.username=cctb jdbc.password=xgj2017
属性文件可以定义多个属性,每个属性都有一个属性名和属性值组成,二者用“=”隔开。
使用PropertyPlaceholderConfigurer属性文件
下面通过PropertyPlaceholderConfigurer引入jdbc.properties属性文件,调整数据源Bean的配置,为了测试,我们引入了JdbcTemplate
beans.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 --> <context:component-scan base- package="com.xgj.ioc.propertyplacehoder"/> <!-- 引入JDBC属性文件 --> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" p:location="classpath:spring/jdbc.properties" p:fileEncoding="utf-8"/> <!-- 通过属性名引用属性值 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="${jdbc.driverClassName}" p:url="${jdbc.url}" p:username="${jdbc.username}" p:password="${jdbc.password}"/> <!-- 配置Jdbc模板 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="dataSource" /> </beans>
通过PropertyPlaceholderConfigurer的location属性引入属性文件,这样在Bean定义的时候就可以引用属性文件中的属性了。
然后通过${jdbc.driverClassName}等占位符来引用jdbc.properties中的属性,这样部署人员仅需要关注jdbc.properties这个配置文件即可,无需关心Spring的配置文件。
测试:
我们数据库中temp_user表有一条记录,我们来模拟登录查询
PropertyPlaceHoderTest.java
package com.xgj.ioc.propertyplacehoder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component; @Component public class PropertyPlaceHoderTest { private final static String MATCH_COUNT_SQL = " SELECT count(*) FROM temp_user " + " WHERE user_name =? and password=? "; private JdbcTemplate jdbcTemplate; @Autowired public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } /** * * * @Title: getMatchCount * * @Description: 根据用户名和密码判断用户是否存在 * * @param username * @param password * * @return: int */ public int getMatchCount(String username, String password) { return jdbcTemplate.queryForObject(MATCH_COUNT_SQL, new Object[] { username, password }, Integer.class); } /** * * * @Title: main * * @Description: 测试 * * @param args * * @return: void */ public static void main(String[] args) { // 加载Spring配置文件 ApplicationContext ctx = new ClassPathXmlApplicationContext( "classpath:com/xgj/ioc/propertyplacehoder/beans.xml"); // 获取通过注解标注的Bean PropertyPlaceHoderTest propertyPlaceHoderTest = ctx.getBean( "propertyPlaceHoderTest", PropertyPlaceHoderTest.class); // 调用方法 int count = propertyPlaceHoderTest.getMatchCount("xgj", "123456"); System.out.println("匹配的用户数量:" + count); } }
运行结果:
PropertyPlacerholderConfigurer的其他属性
locations
如果只有一个属性文件,则直接使用location属性指定即可,如果有多个属性文件,则可以通过locations属性进行设置,可以像配置list一样配置locations属性。
list的配置参考 Spring-注入参数详解-[集合类型属性]
fileEncoding
属性文件的编码格式,Spring默认使用操作系统默认编码读取属性文件,如果属性文件使用了特殊编码,则需要通过该属性显式指定。
order
如果配置配置文件中定义了多个PropertyPlacehoderConfigurer,则通过该属性指定优先顺序。
placeholderPrefix
上面的案例,我们使用${jdbc.driverClassName}引用属性文件中的属性项, 其中, ${
为默认的占位符前缀,可修改
placeholderSuffix
占位符后缀,默认为 }
使用context:property-placehoder引用属性文件
可以使用context命名空间定义属性文件,相比传统的PropertyPlaceholderConfigurer配置,这种方式更优雅
<!-- 引入JDBC属性文件 <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" p:location="classpath:spring/jdbc.properties" p:fileEncoding="utf-8"/> --> <!-- 使用context命名空间,同上面的Bean等效。 --> <context:property-placeholder location="classpath:spring/jdbc.properties" />
但是有个缺点: 如果想自定义一些额外的高级功能,比如属性加密、使用数据库表保存配置信息等,则必须扩展PropertyPlaceholderConfigurer的类并使用Bean的配置方式。
基于注解及基于JAVA类的配置中引用属性
在基于XML的配置文件中,通过${propName}的形式引用属性值,类似的,基于注解的Bean可以通过@Value注解为Bean的成员变量或者方法入参自动注入容器已有的属性。同样的基于JAVA类注解@Configuration的类的引用属性的方式和基于注解配置的引用方式是完全一样的,不再赘述。
实例
package com.xgj.ioc.propertyplacehoder.annotation; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class MyDataSource { private String driveClassName; private String url; private String userName; private String password; /** * * * @Title: setDriveClassName * * @Description: 注入jdbc.driverClassName的值 (也可以直接在属性上注入) * * @param driveClassName * * @return: void */ @Value("${jdbc.driverClassName}") public void setDriveClassName(String driveClassName) { this.driveClassName = driveClassName; } @Value("${jdbc.url}") public void setUrl(String url) { this.url = url; } @Value("${jdbc.username}") public void setUserName(String userName) { this.userName = userName; } @Value("${jdbc.password}") public void setPassword(String password) { this.password = password; } /** * * * @Title: getDriveClassName * * @Description: 获取driveClassName * * @return * * @return: String */ public String getDriveClassName() { System.out.println("getDriveClassName:" + driveClassName); return driveClassName; } public String getUrl() { System.out.println("getUrl:" + url); return url; } public String getUserName() { System.out.println("getUserName:" + userName); return userName; } public String getPassword() { System.out.println("getPassword:" + password); return password; } }
测试类
package com.xgj.ioc.propertyplacehoder.annotation; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AnnotationTest { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext( "classpath:com/xgj/ioc/propertyplacehoder/beans.xml"); MyDataSource myDataSource = ctx.getBean("myDataSource", MyDataSource.class); myDataSource.getDriveClassName(); myDataSource.getUrl(); myDataSource.getUserName(); myDataSource.getPassword(); } }
运行结果
注意事项
使用的过程中,一定要确保所引用的属性值在属性文件中存在且数值匹配,否则会造成Bean创建错误。
比如我们修改一下注入的属性@Value(“${jdbc.driverClassName1}”)
Error creating bean with name ‘myDataSource’: Injection of autowired dependencies failed; …..
2017-08-06 18:23:17,692 INFO [main] (AbstractApplicationContext.java:583) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7fae1081: startup date [Sun Aug 06 18:23:17 BOT 2017]; root of context hierarchy 2017-08-06 18:23:17,767 INFO [main] (XmlBeanDefinitionReader.java:317) - Loading XML bean definitions from class path resource [com/xgj/ioc/propertyplacehoder/beans.xml] 2017-08-06 18:23:18,398 WARN [main] (AbstractApplicationContext.java:551) - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myDataSource': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'jdbc.driverClassName1' in value "${jdbc.driverClassName1}" Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myDataSource': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'jdbc.driverClassName1' in value "${jdbc.driverClassName1}" at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:372) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543) at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139) at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83) at com.xgj.ioc.propertyplacehoder.annotation.AnnotationTest.main(AnnotationTest.java:9) Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'jdbc.driverClassName1' in value "${jdbc.driverClassName1}" at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:174) at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126) at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:236) at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:210) at org.springframework.context.support.PropertySourcesPlaceholderConfigurer$2.resolveStringValue(PropertySourcesPlaceholderConfigurer.java:172) at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:831) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1086) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:659) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366) ... 13 more