思考
- PropertyPlaceholderConfigurer和<context:property-placeholder/>有何区别?
- @Value在Controller层和Service层使用有何不同?
- @Value和${key}在web中配置注意事项。
- 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&characterEncoding=UTF-8&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