早期,如果需要通过spring读取properties文件中的配置信息,都需要在XML文件中配置文件读取方式。

基于XML的读取方式:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
       <list>
     <value>classpath:properties/thread-pool.properties</value>
  </list>
    </property>
</bean>

当然,这种方式可以统一管理properties配置文件,也能实现代码的松耦合。但为了方便开发,提高开发效率,spring官方后来提供了基于注解的配置读取方式。两种方式各有优势,可以基于对项目的考虑选择最合适的方式。接下来就介绍如何通过注解注入properties的配置信息。

首先,准备配置文件:

core.pool.size=2
max.pool.size=3
keep.alive.time=1
task.queue.size=3
await.termination.time=5

定义配置类:

package org.cellphone.config;

import com.google.gson.Gson;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.stereotype.Component;

/**
 *
 */
@Component
@PropertySource("classpath:properties/thread-pool.properties")
public class ThreadPoolConfig {
    /**
     * 核心线程个数
     */
    @Value("${core.pool.size}")
    private int corePoolSize;
    /**
     * 最大线程个数
     */
    @Value("${max.pool.size}")
    private int maxPoolSize;
    /**
     * 保持心跳时间
     */
    @Value("${keep.alive.time}")
    private int keeAliveTime;
    /**
     * 任务队列长度
     */
    @Value("${task.queue.size}")
    private int taskQueueSize;
    /**
     * 等待任务结束的时间
     */
    @Value("${await.termination.time}")
    private int awaitTerminationTime;

    /**
     * 使用@value注解注入properties中的属性
     * 1. 在类名上面使用  @PropertySource("classpath:*") 注解,*代表属性文件路径,可以指向多个配置文件路径
     *      如果是多个配置文件,则是 @PropertySource({"classpath:*","classpath:*"....})
     * 2. 在字段上直接使用@value注解
     * 3. 注解内使用${core.pool.size}  core.pool.size 代表属性文件里面的key
     * 5. 需要新增 PropertySourcesPlaceholderConfigurer 的 bean
     * 6. 在 PropertySourcesPlaceholderConfigurer 增加@bean注解,申明返回的是一个bean,否则会注入失败
     *
     */



    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    public int getCorePoolSize() {
        return corePoolSize;
    }

    public void setCorePoolSize(int corePoolSize) {
        this.corePoolSize = corePoolSize;
    }

    public int getMaxPoolSize() {
        return maxPoolSize;
    }

    public void setMaxPoolSize(int maxPoolSize) {
        this.maxPoolSize = maxPoolSize;
    }

    public int getKeeAliveTime() {
        return keeAliveTime;
    }

    public void setKeeAliveTime(int keeAliveTime) {
        this.keeAliveTime = keeAliveTime;
    }

    public int getTaskQueueSize() {
        return taskQueueSize;
    }

    public void setTaskQueueSize(int taskQueueSize) {
        this.taskQueueSize = taskQueueSize;
    }

    public int getAwaitTerminationTime() {
        return awaitTerminationTime;
    }

    public void setAwaitTerminationTime(int awaitTerminationTime) {
        this.awaitTerminationTime = awaitTerminationTime;
    }

    @Override
    public String toString() {
        return new Gson().toJson(this);
    }
}

这里注入了一个 PropertySourcesPlaceholderConfigurer  bean,spring是通过 PropertySourcesPlaceholderConfigurer 的 locations 来查找属性文件,然后再根据注解将匹配的属性set进去,下面通过源码来了解注解可以进行一些什么操作。

public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware {

    /**
     * {@value} is the name given to the {@link PropertySource} for the set of
     * {@linkplain #mergeProperties() merged properties} supplied to this configurer.
     */
    public static final String LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME = "localProperties";

    /**
     * {@value} is the name given to the {@link PropertySource} that wraps the
     * {@linkplain #setEnvironment environment} supplied to this configurer.
     */
    public static final String ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME = "environmentProperties";


    @Nullable
    private MutablePropertySources propertySources;

    @Nullable
    private PropertySources appliedPropertySources;

    @Nullable
    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>

属性注释:

/** Default placeholder prefix: {@value} */
    public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";

    /** Default placeholder suffix: {@value} */
    public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";

    /** Default value separator: {@value} */
    public static final String DEFAULT_VALUE_SEPARATOR = ":";


    /** Defaults to {@value #DEFAULT_PLACEHOLDER_PREFIX} */
    protected String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX;

    /** Defaults to {@value #DEFAULT_PLACEHOLDER_SUFFIX} */
    protected String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX;

    /** Defaults to {@value #DEFAULT_VALUE_SEPARATOR} */
    @Nullable
    protected String valueSeparator = DEFAULT_VALUE_SEPARATOR;

    protected boolean trimValues = false;

    @Nullable
    protected String nullValue;

    protected boolean ignoreUnresolvablePlaceholders = false;

从上面注解可以发现,使用的 默认前缀是:'${',而后缀是:'}',默认的分隔符是 ':',但是set方法可以替换掉默认的分隔符,而  ignoreUnresolvablePlaceholders  默认为 false,表示会开启配置文件不存在,抛出异常的错误。

从上面就能看出这个bean所起的作用,就是将 @propertySource 注解的bean注入属性的作用,如果没有该bean,则不能解析${}符号。

接下来执行单元测试。

配置类代码:

package org.cellphone.web;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * 配置类——用来替换xml配置文件
 */
@Configuration
@ComponentScan("org.cellphone.config")
public class SpringConfig {
}
package org.cellphone.web;

import org.cellphone.config.ThreadPoolConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * 纯注解方式整合Junit单元测试框架测试类
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { SpringConfig.class }) // 需要注意此处,将加载配置文件的注解换成加载配置类的注解
public class ThreadPoolConfigTest {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private ThreadPoolConfig threadPoolConfig;

    @Test
    public void testThreadPoolConfig() {
        logger.info(threadPoolConfig.toString());
    }
}

使用  @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")
      }
)