早期,如果需要通过spring读取properties文件中的配置信息,都需要在XML文件中配置文件读取方式。
基于XML的读取方式:
1 <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 2 <property name="locations"> 3 <list> 4 <value>classpath:properties/thread-pool.properties</value> 5 </list> 6 </property> 7 </bean>
当然,这种方式可以统一管理properties配置文件,也能实现代码的松耦合。但为了方便开发,提高开发效率,spring官方后来提供了基于注解的配置读取方式。两种方式各有优势,可以基于对项目的考虑选择最合适的方式。接下来就介绍如何通过注解注入properties的配置信息。
首先,准备配置文件:
1 core.pool.size=2 2 max.pool.size=3 3 keep.alive.time=1 4 task.queue.size=3 5 await.termination.time=5
定义配置类:
1 package org.cellphone.config; 2 3 import com.google.gson.Gson; 4 import org.springframework.beans.factory.annotation.Value; 5 import org.springframework.context.annotation.Bean; 6 import org.springframework.context.annotation.PropertySource; 7 import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; 8 import org.springframework.stereotype.Component; 9 10 /** 11 * 12 */ 13 @Component 14 @PropertySource("classpath:properties/thread-pool.properties") 15 public class ThreadPoolConfig { 16 /** 17 * 核心线程个数 18 */ 19 @Value("${core.pool.size}") 20 private int corePoolSize; 21 /** 22 * 最大线程个数 23 */ 24 @Value("${max.pool.size}") 25 private int maxPoolSize; 26 /** 27 * 保持心跳时间 28 */ 29 @Value("${keep.alive.time}") 30 private int keeAliveTime; 31 /** 32 * 任务队列长度 33 */ 34 @Value("${task.queue.size}") 35 private int taskQueueSize; 36 /** 37 * 等待任务结束的时间 38 */ 39 @Value("${await.termination.time}") 40 private int awaitTerminationTime; 41 42 /** 43 * 使用@value注解注入properties中的属性 44 * 1. 在类名上面使用 @PropertySource("classpath:*") 注解,*代表属性文件路径,可以指向多个配置文件路径 45 * 如果是多个配置文件,则是 @PropertySource({"classpath:*","classpath:*"....}) 46 * 2. 在字段上直接使用@value注解 47 * 3. 注解内使用${core.pool.size} core.pool.size 代表属性文件里面的key 48 * 5. 需要新增 PropertySourcesPlaceholderConfigurer 的 bean 49 * 6. 在 PropertySourcesPlaceholderConfigurer 增加@bean注解,申明返回的是一个bean,否则会注入失败 50 * 51 */ 52 53 54 55 @Bean 56 public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { 57 return new PropertySourcesPlaceholderConfigurer(); 58 } 59 60 public int getCorePoolSize() { 61 return corePoolSize; 62 } 63 64 public void setCorePoolSize(int corePoolSize) { 65 this.corePoolSize = corePoolSize; 66 } 67 68 public int getMaxPoolSize() { 69 return maxPoolSize; 70 } 71 72 public void setMaxPoolSize(int maxPoolSize) { 73 this.maxPoolSize = maxPoolSize; 74 } 75 76 public int getKeeAliveTime() { 77 return keeAliveTime; 78 } 79 80 public void setKeeAliveTime(int keeAliveTime) { 81 this.keeAliveTime = keeAliveTime; 82 } 83 84 public int getTaskQueueSize() { 85 return taskQueueSize; 86 } 87 88 public void setTaskQueueSize(int taskQueueSize) { 89 this.taskQueueSize = taskQueueSize; 90 } 91 92 public int getAwaitTerminationTime() { 93 return awaitTerminationTime; 94 } 95 96 public void setAwaitTerminationTime(int awaitTerminationTime) { 97 this.awaitTerminationTime = awaitTerminationTime; 98 } 99 100 @Override 101 public String toString() { 102 return new Gson().toJson(this); 103 } 104 }
这里注入了一个 PropertySourcesPlaceholderConfigurer bean,spring是通过 PropertySourcesPlaceholderConfigurer 的 locations 来查找属性文件,然后再根据注解将匹配的属性set进去,下面通过源码来了解注解可以进行一些什么操作。
1 public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware { 2 3 /** 4 * {@value} is the name given to the {@link PropertySource} for the set of 5 * {@linkplain #mergeProperties() merged properties} supplied to this configurer. 6 */ 7 public static final String LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME = "localProperties"; 8 9 /** 10 * {@value} is the name given to the {@link PropertySource} that wraps the 11 * {@linkplain #setEnvironment environment} supplied to this configurer. 12 */ 13 public static final String ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME = "environmentProperties"; 14 15 16 @Nullable 17 private MutablePropertySources propertySources; 18 19 @Nullable 20 private PropertySources appliedPropertySources; 21 22 @Nullable 23 private Environment environment;
下面代码省略。。。
上面源码并没能说明为什么一定要返回这个bean,接下来看父类 PlaceholderConfigurerSupport 的源码:
1 /** 2 * Abstract base class for property resource configurers that resolve placeholders 3 * in bean definition property values. Implementations <em>pull</em> values from a 4 * properties file or other {@linkplain org.springframework.core.env.PropertySource 5 * property source} into bean definitions. 6 * 7 * <p>The default placeholder syntax follows the Ant / Log4J / JSP EL style: 8 * 9 * <pre class="code">${...}</pre> 10 * 11 * Example XML bean definition: 12 * 13 * <pre class="code"> 14 * <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"/> 15 * <property name="driverClassName" value="${driver}"/> 16 * <property name="url" value="jdbc:${dbname}"/> 17 * </bean> 18 * </pre> 19 * 20 * Example properties file: 21 * 22 * <pre class="code">driver=com.mysql.jdbc.Driver 23 * dbname=mysql:mydb</pre> 24 * 25 * Annotated bean definitions may take advantage of property replacement using 26 * the {@link org.springframework.beans.factory.annotation.Value @Value} annotation: 27 * 28 * <pre class="code">@Value("${person.age}")</pre> 29 * 30 * Implementations check simple property values, lists, maps, props, and bean names 31 * in bean references. Furthermore, placeholder values can also cross-reference 32 * other placeholders, like: 33 * 34 * <pre class="code">rootPath=myrootdir 35 * subPath=${rootPath}/subdir</pre> 36 * 37 * In contrast to {@link PropertyOverrideConfigurer}, subclasses of this type allow 38 * filling in of explicit placeholders in bean definitions. 39 * 40 * <p>If a configurer cannot resolve a placeholder, a {@link BeanDefinitionStoreException} 41 * will be thrown. If you want to check against multiple properties files, specify multiple 42 * resources via the {@link #setLocations locations} property. You can also define multiple 43 * configurers, each with its <em>own</em> placeholder syntax. Use {@link 44 * #ignoreUnresolvablePlaceholders} to intentionally suppress throwing an exception if a 45 * placeholder cannot be resolved. 46 * 47 * <p>Default property values can be defined globally for each configurer instance 48 * via the {@link #setProperties properties} property, or on a property-by-property basis 49 * using the default value separator which is {@code ":"} by default and 50 * customizable via {@link #setValueSeparator(String)}. 51 * 52 * <p>Example XML property with default value: 53 * 54 * <pre class="code"> 55 * <property name="url" value="jdbc:${dbname:defaultdb}"/> 56 * </pre> 57 * 58 * @author Chris Beams 59 * @author Juergen Hoeller 60 * @since 3.1 61 * @see PropertyPlaceholderConfigurer 62 * @see org.springframework.context.support.PropertySourcesPlaceholderConfigurer 63 */ 64 public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfigurer 65 implements BeanNameAware, BeanFactoryAware { 66 67 /** Default placeholder prefix: {@value} */ 68 public static final String DEFAULT_PLACEHOLDER_PREFIX = "${"; 69 70 /** Default placeholder suffix: {@value} */ 71 public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}"; 72 73 /** Default value separator: {@value} */ 74 public static final String DEFAULT_VALUE_SEPARATOR = ":"; 75 76 77 /** Defaults to {@value #DEFAULT_PLACEHOLDER_PREFIX} */ 78 protected String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX; 79 80 /** Defaults to {@value #DEFAULT_PLACEHOLDER_SUFFIX} */ 81 protected String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX; 82 83 /** Defaults to {@value #DEFAULT_VALUE_SEPARATOR} */ 84 @Nullable 85 protected String valueSeparator = DEFAULT_VALUE_SEPARATOR; 86 87 protected boolean trimValues = false; 88 89 @Nullable 90 protected String nullValue; 91 92 protected boolean ignoreUnresolvablePlaceholders = false; 93 94 @Nullable 95 private String beanName; 96 97 @Nullable 98 private BeanFactory beanFactory;
下面代码省略。。。
类注释说明了 PlaceholderConfigurerSupport 类所起的作用,以及替换了XML的哪些操作,其中就描述了注入的bean可以利用 @Value 注解进行属性替换:
* Annotated bean definitions may take advantage of property replacement using * the {@link org.springframework.beans.factory.annotation.Value @Value} annotation: * * <pre class="code">@Value("${person.age}")</pre>
属性注释:
1 /** Default placeholder prefix: {@value} */ 2 public static final String DEFAULT_PLACEHOLDER_PREFIX = "${"; 3 4 /** Default placeholder suffix: {@value} */ 5 public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}"; 6 7 /** Default value separator: {@value} */ 8 public static final String DEFAULT_VALUE_SEPARATOR = ":"; 9 10 11 /** Defaults to {@value #DEFAULT_PLACEHOLDER_PREFIX} */ 12 protected String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX; 13 14 /** Defaults to {@value #DEFAULT_PLACEHOLDER_SUFFIX} */ 15 protected String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX; 16 17 /** Defaults to {@value #DEFAULT_VALUE_SEPARATOR} */ 18 @Nullable 19 protected String valueSeparator = DEFAULT_VALUE_SEPARATOR; 20 21 protected boolean trimValues = false; 22 23 @Nullable 24 protected String nullValue; 25 26 protected boolean ignoreUnresolvablePlaceholders = false;
从上面注解可以发现,使用的 默认前缀是:'${',而后缀是:'}',默认的分隔符是 ':',但是set方法可以替换掉默认的分隔符,而 ignoreUnresolvablePlaceholders 默认为 false,表示会开启配置文件不存在,抛出异常的错误。
从上面就能看出这个bean所起的作用,就是将 @propertySource 注解的bean注入属性的作用,如果没有该bean,则不能解析${}符号。
接下来执行单元测试。
配置类代码:
1 package org.cellphone.web; 2 3 import org.springframework.context.annotation.ComponentScan; 4 import org.springframework.context.annotation.Configuration; 5 6 /** 7 * 配置类——用来替换xml配置文件 8 */ 9 @Configuration 10 @ComponentScan("org.cellphone.config") 11 public class SpringConfig { 12 }
1 package org.cellphone.web; 2 3 import org.cellphone.config.ThreadPoolConfig; 4 import org.junit.Test; 5 import org.junit.runner.RunWith; 6 import org.slf4j.Logger; 7 import org.slf4j.LoggerFactory; 8 import org.springframework.beans.factory.annotation.Autowired; 9 import org.springframework.test.context.ContextConfiguration; 10 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 12 /** 13 * 纯注解方式整合Junit单元测试框架测试类 14 */ 15 @RunWith(SpringJUnit4ClassRunner.class) 16 @ContextConfiguration(classes = { SpringConfig.class }) // 需要注意此处,将加载配置文件的注解换成加载配置类的注解 17 public class ThreadPoolConfigTest { 18 19 private final Logger logger = LoggerFactory.getLogger(getClass()); 20 21 @Autowired 22 private ThreadPoolConfig threadPoolConfig; 23 24 @Test 25 public void testThreadPoolConfig() { 26 logger.info(threadPoolConfig.toString()); 27 } 28 }
使用 @PropertySource 注解需要注意以下几个地方:
1. 使用注解需要将类申明为一个bean,可以使用 @Component 注解;
2. @PropertySource(value = "classpath:properties/config_userbean.properties", ignoreResourceNotFound = true) 表示注入配置文件,并且忽略配置文件不存在的异常;
3. 必须返回一个 PropertySourcesPlaceholderConfigurer 的bean,否则会不能识别@Value("${core.pool.size}") 注解中的 ${core.pool.size}指向的value,而会注入${core.pool.size}的字符串,返回 PropertySourcesPlaceholderConfigurer 的方法,使用 @Bean 注解,表示返回的是个bean。
在spring 4.0以后,spring增加了 @PropertySources 注解,可以使用多个 @PropertySource 注解,如下:
@PropertySources( { @PropertySource("classpath:properties/thread-pool.properties"), @PropertySource("classpath:properties/mysql.properties") } )