您的位置:首页 > 编程语言 > Java开发

Spring中property-placeholder的使用与解析

2017-01-18 14:47 393 查看
我们在基于spring开发应用的时候,一般都会将数据库的配置放置在properties文件中.

代码分析的时候,涉及的知识点概要:
NamespaceHandler 解析xml配置文件中的自定义命名空间
ContextNamespaceHandler 上下文相关的解析器,这边定义了具体如何解析property-placeholder的解析器
BeanDefinitionParser 解析bean definition的接口
BeanFactoryPostProcessor 加载好bean definition后可以对其进行修改
PropertySourcesPlaceholderConfigurer 处理bean definition 中的占位符

我们先来看看具体的使用吧


property的使用


在xml文件中配置properties文件

<?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:context="http://www.springframework.org/schema/context"   xsi:schemaLocation="      http://www.springframework.org/schema/beans      http://www.springframework.org/schema/beans/spring-beans-4.2.xsd      http://www.springframework.org/schema/context      http://www.springframework.org/schema/context/spring-context-4.2.xsd"> 
<context:property-placeholder location="classpath:foo.properties"/>

</beans>


这样/src/main/resources/foo.properties文件就会被spring加载

如果想使用多个配置文件,可以添加order字段来进行排序


使用PropertySource注解配置

Spring3.1添加了@PropertySource注解,方便添加property文件到环境.

@Configuration
@PropertySource("classpath:foo.properties")
public class PropertiesWithJavaConfig {

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



properties的注入与使用

java中使用@Value注解获取

@Value( "${jdbc.url}" )
private String jdbcUrl;


还可以添加一个默认值

@Value( "${jdbc.url:aDefaultUrl}" )
private String jdbcUrl;


在Spring的xml配置文件中获取

<bean id="dataSource">
<property name="url" value="${jdbc.url}" />
</bean>



源码解析


properties配置信息的加载

Spring在启动时会通过AbstractApplicationContext#refresh启动容器初始化工作,期间会委托loadBeanDefinitions解析xml配置文件.

protectedfinalvoidrefreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}


loadBeanDefinitions通过层层委托,找到DefaultBeanDefinitionDocumentReader#parseBeanDefinition解析具体的bean

protectedvoidparseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}


这边由于不是标准类定义,所以委托BeanDefinitionParserDelegate解析

通过NamespaceHandler查找到对应的处理器是ContextNamespaceHandler,再通过id找到PropertyPlaceholderBeanDefinitionParser解析器解析

@Override
publicvoidinit() {
// 这就是我们要找的解析器
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}


PropertyPlaceholderBeanDefinitionParser是这一轮代码分析的重点.

我们来看看它的父类吧.

BeanDefinitionParser

被DefaultBeanDefinitionDocumentReader用于解析个性化的标签

这边只定义了一个解析Element的parse api

public interface BeanDefinitionParser {
BeanDefinition parse(Element element, ParserContext parserContext);
}


AbstractBeanDefinitionParser

BeanDefinitionParser接口的默认抽象实现.spring的拿手好戏,这边提供了很多方便使用的api,并使用模板方法设计模式给子类提供自定义实现的钩子

我们来看看parse时具体的处理逻辑把:
调用钩子parseInternal解析
生成bean id,使用BeanNameGenerator生成,或者直接读取id属性
处理name 与别名aliases
往容器中注册bean
进行事件触发

AbstractSingleBeanDefinitionParser

解析,定义单个BeanDefinition的抽象父类

在parseInternal中,解析出parentName,beanClass,source;并使用BeanDefinitionBuilder进行封装

AbstractPropertyLoadingBeanDefinitionParser

解析property相关的属性,如location,properties-ref,file-encoding,order等

PropertyPlaceholderBeanDefinitionParser

这边处理的事情不多,就是设置ingore-unresolvable和system-properties-mode


properties文件的加载,bean的实例化

接下来,我们再看看这个bean是在什么时候实例化的,一般类的实例化有2种,一种是单例系统启动就实例化;一种是非单例(或者单例懒加载)在getBean时实例化.

这边的触发却是通过BeanFcatoryPostProcessor.

BeanFactoryPostProcessor是在bean实例化前,修改bean definition的,比如bean definition中的占位符就是这边解决的,而我们现在使用的properties也是这边解决的.

这个是通过PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors实现的.

扫描容器中的BeanFactoryPostProcessor,找到了这边需要的PropertySourcesPlaceholderConfigurer,并通过容器的getBean实例化

protectedvoidinvokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
}


PropertySourcesPlaceholderConfigurer实例化完成后,就直接进行触发,并加载信息

OrderComparator.sort(priorityOrderedPostProcessors);
invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);


我们再来看看PropertySourcesPlaceholderConfigurer的继承体系把

BeanFactoryPostProcessor

定义一个用于修改容器中bean definition的属性的接口.其实现类在一般类使用前先实例化,并对其他类的属性进行修改.

这跟BeanPostProcessor有明显的区别,BeanPostProcessor是修改bean实例的.

PropertiesLoaderSupport

加载properties文件的抽象类.

这边具体的加载逻辑是委托PropertiesLoaderUtils#fillProperties实现

PropertyResourceConfigurer

bean definition中占位符的替换就是这个抽象类实现的.

实现BeanFactoryPostProcessor#postProcessBeanFactory,迭代容器的中的类定义,进行修改

具体如何修改就通过钩子processProperties交由子类实现

PlaceholderConfigurerSupport

使用visitor设计模式,通过BeanDefinitionVisitor和StringValueResolver更新属性

StringValueResolver是一个转化String类型数据的接口,真正更新属性的api实现竟然是在PropertyPlaceholderHelper#parseStringValue

PropertySourcesPlaceholderConfigurer

覆写postProcessorBeanFactory api定义解析流程

context:property-placeholder大大的方便了我们数据库的配置。

[html] view
plain copy

只需要在spring的配置文件里添加一句:<context:property-placeholder?location="classpath:jdbc.properties"/>?即可,这里location值为参数配置文件的位置,参数配置文件通常放在src目录下,而参数配置文件的格式跟java通用的参数配置文件相同,即键值对的形式,例如:  

  

#jdbc配置  

  

test.jdbc.driverClassName=com.mysql.jdbc.Driver  

test.jdbc.url=jdbc:mysql://localhost:3306/test  

test.jdbc.username=root  

test.jdbc.password=root  

这样一来就可以为spring配置的bean的属性设置值了,比如spring有一个jdbc数据源的类DriverManagerDataSource

在配置文件里这么定义bean:

<bean id="testDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${test.jdbc.driverClassName}"/>
<property name="url" value="${test.jdbc.url}"/>
<property name="username" value="${test.jdbc.username}"/>
<property name="password" value="${test.jdbc.password}"/>
</bean>
这样修改起来也方便,也统一的这个规范。

另外需要注意的是,如果遇到下面着着这种问题:

A模块和B模块都分别拥有自己的Spring XML配置,并分别拥有自己的配置文件:

A模块的Spring配置文件如下:

[html] view
plain copy

<?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:context="http://www.springframework.org/schema/context"  

       xmlns:p="http://www.springframework.org/schema/p"  

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd  

       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">  

   <context:property-placeholder location="classpath*:conf/conf_a.properties"/>  

   <bean class="com.xxx.aaa.Bean1"  

          p:driverClassName="${modulea.jdbc.driverClassName}"  

          p:url="${modulea.jdbc.url}"  

          p:username="${modulea.jdbc.username}"  

          p:password="${modulea.jdbc.password}"/>  

</beans>  

conf/conf_a.properties:

[html] view
plain copy

modulea.jdbc.driverClassName=com.mysql.jdbc.Driver  

modulea.jdbc.username=cartan  

modulea.jdbc.password=superman  

modulea.jdbc.url=jdbc:mysql://127.0.0.1:3306/modulea?useUnicode=true&characterEncoding=utf8  

B模块的Spring配置文件如下:

[html] view
plain copy

<?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:context="http://www.springframework.org/schema/context"    

       xmlns:p="http://www.springframework.org/schema/p"    

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd    

       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">    

   <context:property-placeholder location="classpath*:conf/conf_b.properties"/>    

   <bean class="com.xxx.bbb.Bean1"    

          p:driverClassName="${moduleb.jdbc.driverClassName}"    

          p:url="${moduleb.jdbc.url}"    

          p:username="${moduleb.jdbc.username}"    

          p:password="${moduleb.jdbc.password}"/>    

</beans>    

conf/conf_b.properties:

[html] view
plain copy

moduleb.jdbc.driverClassName=com.mysql.jdbc.Driver  

moduleb.jdbc.username=cartan  

moduleb.jdbc.password=superman  

moduleb.jdbc.url=jdbc:mysql://127.0.0.1:3306/modulea?useUnicode=true&characterEncoding=utf8  

单独运行A模块,或单独运行B模块都是正常的,但将A和B两个模块集成后运行,Spring容器就启动不了了:

Could not resolve placeholder 'moduleb.jdbc.driverClassName' in string value "${moduleb.jdbc.driverClassName}"

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

spring容器中最多只能定义一个context:property-placeholder,不然就出现那种个错误,那如何来解决上面的问题呢?

A和B模块去掉

[html] view
plain copy

<context:property-placeholder location="classpath*:conf/conf_b.properties"/>   

然后重新写个xml:

[html] view
plain copy

<?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:context="http://www.springframework.org/schema/context"  

       xmlns:p="http://www.springframework.org/schema/p"  

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd  

       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">  

   <context:property-placeholder location="classpath*:conf/conf*.properties"/>  

   <import resource="a.xml"/>  

   <import resource="b.xml"/>  

</beans>  

Spring中如何配置Hibernate事务 http://www.linuxidc.com/Linux/2013-12/93681.htm

Struts2整合Spring方法及原理 http://www.linuxidc.com/Linux/2013-12/93692.htm

基于 Spring 设计并实现 RESTful Web Services http://www.linuxidc.com/Linux/2013-10/91974.htm

Spring-3.2.4 + Quartz-2.2.0集成实例 http://www.linuxidc.com/Linux/2013-10/91524.htm

使用 Spring 进行单元测试 http://www.linuxidc.com/Linux/2013-09/89913.htm

运用Spring注解实现Netty服务器端UDP应用程序 http://www.linuxidc.com/Linux/2013-09/89780.htm

Spring 3.x 企业应用开发实战 PDF完整高清扫描版+源代码 http://www.linuxidc.com/Linux/2013-10/91357.htm

Spring 的详细介绍请点这里
Spring 的下载地址请点这里
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: