PropertyPlaceholderConfigurer使用及@Value使用注意事项

 思考 

  1. PropertyPlaceholderConfigurer和<context:property-placeholder/>有何区别?
  2. @Value在Controller层和Service层使用有何不同?
  3. @Value和${key}在web中配置注意事项。
  4. PropertyPlaceholderConfigurer在容器里能存在多个bean吗 ?

关于PropertyPlaceholderConfigurer

1. PropertyPlaceholderConfigurer是个bean工厂后置处理器的实现,也就是 BeanFactoryPostProcessor接口的一个实现。PropertyPlaceholderConfigurer可以将上下文(配置文件)中的属性值放在另一个单独的标准java Properties文件中去。在XML文件中用${key}替换指定的properties文件中的值。这样的话,只需要对properties文件进行修改,而不用对xml配置文件进行修改。

2.在Spring中,使用PropertyPlaceholderConfigurer可以在XML配置文件中加入外部属性文件,当然也可以指定外部文件的编码,如:

<bean id="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
   <property name="location">
     <value>conf/sqlmap/jdbc.properties</value>
   </property>
    <property name="fileEncoding">
      <value>UTF-8</value>
    </property>
</bean>

 当然也可以引入多个属性文件,如:

<bean id="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="locations">
   <list>
    <value>/WEB-INF/mail.properties</value>  
    <value>classpath: conf/sqlmap/jdbc.properties</value>//注意这两种value值的写法
   </list>
  </property>
</bean>

 譬如,jdbc.properties的内容为:

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/mysqldb?useUnicode=true&amp;characterEncoding=UTF-8&amp;zeroDateTimeBehavior=round;
jdbc.username=root
jdbc.password=123456

 那么在spring配置文件中,我们就可以这样写:

<bean id="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="locations">
   <list>
    <value>classpath: conf/sqlmap/jdbc.properties </value>
   </list>
  </property>
</bean>
<bean id="dataSource"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">
  <property name="driverClassName"value="${jdbc.driverClassName}" />
  <property name="url" value="${jdbc.url}" />
  <property name="username" value="${jdbc.username}"/>
  <property name="password"value="${jdbc.password}" />
</bean>

 这样,一个简单的数据源就设置完毕了。可以看出:PropertyPlaceholderConfigurer起的作用就是将占位符指向的数据库配置信息放在bean中定义的工具。

查看源代码,可以发现,locations属性定义在PropertyPlaceholderConfigurer的祖父类 PropertiesLoaderSupport中,而location只有 setter方法。类似于这样的配置,在spring的源程序中很常见的。

PropertyPlaceholderConfigurer内置的功能非常丰富,如果它未找到${xxx}中定义的xxx键,它还会去JVM系统属性(System.getProperty())和环境变量(System.getenv())中寻找。通过启用systemPropertiesMode和searchSystemEnvironment属性,开发者能够控制这一行为。

我们可以通过System.setProperty(key, value)或者java中通过-Dnamevalue来给Spring配置文件传递参数。

 为简化PropertyPlaceholderConfigurer的使用,Spring提供了<context:property-placeholder/>元素。下面给出了配置示例,启用它后,开发者便不用配置PropertyPlaceholderConfigurer对象了。

<context:property-placeholder location="userinfo.properties"/> 

 注意:Spring容器采用反射扫描的发现机制,在探测到Spring容器中有一个org.springframework.beans.factory.config.PropertyPlaceholderConfigurer的Bean就会停止对剩余PropertyPlaceholderConfigurer的扫描。

     即spring容器中最多只能定义一个context:property-placeholder!(spring和springmvc不是同一容器,PropertyPlaceholderConfigurer可以同时存在于spring和springmvc中)。 

 @Value使用注意事项 

项目中经常会用到配置文件,定义成properties的形式比较常见,为了方便使用一般在spring配置文件中做如下配置:

<context:property-placeholder ignore-unresolvable="true" location="classpath:application.properties" />

这样在程序代码中直接用@Value("${name}")就能直接取到properties文件中定义的变量值。

但是发现一个情况,在Controller中取不到这个值,直接输出了${name}字符串,并没有解析出值,而在service中却能取到。有点奇怪啊,明显在Controller中貌似并没有引入properties文件中的变量,而被当做普通的字符串处理了。突然想到这个项目有2个配置文件,1个在WEB-INF下的springmvc-servlet.xml,1个在classpath下的applicationContext.xml,其中applicationContext.xml中定义有placeholder。

说实话之前并没有注意过这个配置文件的区别,一直以为只是放置的位置不一样而已,借助这次机会吧,查询到了一些资料。先看一则在web.xml中引入spring的配置:

<!-- springmvc配置开始 -->
<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 可以自定义servlet.xml配置文件的位置和名称,默认为WEB-INF目录下,名称为[<servlet-name>]-servlet.xml,如spring-servlet.xml
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring-servlet.xml</param-value>
    </init-param>
    -->
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>*.do</url-pattern>
</servlet-mapping>
<!-- springmvc配置结束 -->
<!-- Spring配置开始 --> 
<listener> 
   <listenerclass>org.springframework.web.context.ContextLoaderListener</listener-class> 
</listener> 
<!-- 指定Spring Bean的配置文件所在目录。默认配置在WEB-INF目录下 --> 
<context-param> 
   <param-name>contextConfigLocation</param-name> 
   <param-value>classpath:config/applicationContext.xml</param-value> 
</context-param>
<!-- Spring配置结束 -->

 可以看到分为spring配置和springmvc配置2种。其中spring配置以监听器的形式引入,不指定xml配置文件地址则默认查找WEB-INF下的applicationContext.xml文件。springmvc则以 servlet形式引入,当没有指定引入的xml配置文件地址时,则会自动引入WEB-INF下的[servlet-name]-servlet.xml文件,本例中为springmvc-servlet.xml。引入顺序为先引入spring配置,再引入servlet形式的springmvc配置。

值得注意的几点是:springmvc的配置文件中可以直接用id引入spring配置文件中定义的bean,但是反过来不可以。每一个springmvc的配置文件xxx-servlet.xml对应一个web.xml中的servlet定义。当存在多个springmvc配置文件时候,他们之间是不能互相访问的。

在百度中别人的帖子中看到一段应该是官方的原文解释,我摘抄过来并粗糙的直译一下:

Spring lets you define multiple contexts in a parent-child hierarchy.
spring允许你定义多个上下文在父子继承关系中
The applicationContext.xml defines the beans for the "root webapp context", i.e. the context associated with the webapp.
applicationContext.xml文件是为了"根webapp应用上下文"定义bean,也就是说它的上下文是和webapp相关联的
The spring-servlet.xml (or whatever else you call it) defines the beans for one servlet's app context. There can be many of these in a webapp, 
spring-servlet.xml文件(或是其他的你习惯的称呼)是为了一个servlet应用上下文定义bean.在一个webapp中可以有多个此配置文件,
one per Spring servlet (e.g. spring1-servlet.xml for servlet spring1, spring2-servlet.xml for servlet spring2). 每一个配置对应一个spring的servlelt(例如: 名为spring1的servlet拥有配置文件spring1-servlet.xml, 名为spring2的servlet拥有配置文件spring2-servlet.xml). Beans in spring-servlet.xml can reference beans in applicationContext.xml, but not vice versa. 在spring-servlet.xml中定义的bean可以直接引用在applicationContext.xml中定义的bean, 但是反过来不可以. All Spring MVC controllers must go in the spring-servlet.xml context. 所有springmvc的Controller必须在spring-servlet.xml对应的上下文中运行. In most simple cases, the applicationContext.xml context is unnecessary. It is generally used to contain beans that are shared between all servlets
在大多数简单的情况下, applicationContext.xml对应的上下文并不必须.它通常用来包含那些bean用来在webapp中所有servlet之间共享.
in a webapp. If you only have one servlet, then there's not really much point, unless you have a specific use for it.
如果你只有一个servlet, 那么实际没有什么必要定义applicationContext.xml, 除非你有特别应用.

@Value在哪个容器中使用,就调用哪个容器中的配置,因为Controller是定义在子容器中的,故在Controller中使用@Value也是在子容器中操作,而项目中placeholder是定义在父容器中的,在子容器中实际上没有它的定义,也就是并没有引入properties文件中的值。虽然子容器能访问父容器,但是如果不引入的话,子容器中实际上是没有父容器中的properties值。所以只需要在子容器中的配置中加入<context:property-placeholder ignore-unresolvable="true" location="classpath*:/application.properties" />就一切正常了。

那么回到最开始的@Value取不到值的问题,现在的可以清楚由于Controller是定义在springmvc的servlet配置文件中的,所以在Controller中使用@Value注解会从springmvc的配置中查找,故只需要将placeholder重新在springmvc的配置中配置一遍,Controller中的@Value注解便能取到值了。

 注意: 

1.即使给变量赋了初值也会以配置文件的值为准 
2.用@Value注解只能在spring管理的bean中使用,在非spring管理的工具类中,只能通过静态方法读取配置文件

参考资料

http://www.cnblogs.com/chyu/p/4779265.html

http://www.cnblogs.com/huqianliang/p/5673701.html

https://stackoverflow.com/questions/11890544/spring-value-annotation-in-controller-class-not-evaluating-to-value-inside-pro

原文地址:https://www.cnblogs.com/junzi2099/p/8042336.html