Spring中property-placeholder的使用与解析

我们在基于spring开发应用的时候,一般都会将数据库的配置放置在properties文件中.
代码分析的时候,涉及的知识点概要:

  1. NamespaceHandler 解析xml配置文件中的自定义命名空间
  2. ContextNamespaceHandler 上下文相关的解析器,这边定义了具体如何解析property-placeholder的解析器
  3. BeanDefinitionParser 解析bean definition的接口
  4. BeanFactoryPostProcessor 加载好bean definition后可以对其进行修改
  5. 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的注入与使用
  1. java中使用@Value注解获取

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

还可以添加一个默认值

@Value( "${jdbc.url:aDefaultUrl}" )
private String jdbcUrl;
  1. 在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是这一轮代码分析的重点.
我们来看看它的父类吧.

  1. BeanDefinitionParser
    被DefaultBeanDefinitionDocumentReader用于解析个性化的标签
    这边只定义了一个解析Element的parse api

    public interface BeanDefinitionParser {
    BeanDefinition parse(Element element, ParserContext parserContext);
    }
  2. AbstractBeanDefinitionParser
    BeanDefinitionParser接口的默认抽象实现.spring的拿手好戏,这边提供了很多方便使用的api,并使用模板方法设计模式给子类提供自定义实现的钩子
    我们来看看parse时具体的处理逻辑把:
    • 调用钩子parseInternal解析
    • 生成bean id,使用BeanNameGenerator生成,或者直接读取id属性
    • 处理name 与别名aliases
    • 往容器中注册bean
    • 进行事件触发
  3. AbstractSingleBeanDefinitionParser
    解析,定义单个BeanDefinition的抽象父类
    在parseInternal中,解析出parentName,beanClass,source;并使用BeanDefinitionBuilder进行封装

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

  5. 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的继承体系把

  1. BeanFactoryPostProcessor
    定义一个用于修改容器中bean definition的属性的接口.其实现类在一般类使用前先实例化,并对其他类的属性进行修改.
    这跟BeanPostProcessor有明显的区别,BeanPostProcessor是修改bean实例的.

  2. PropertiesLoaderSupport
    加载properties文件的抽象类.
    这边具体的加载逻辑是委托PropertiesLoaderUtils#fillProperties实现

  3. PropertyResourceConfigurer
    bean definition中占位符的替换就是这个抽象类实现的.
    实现BeanFactoryPostProcessor#postProcessBeanFactory,迭代容器的中的类定义,进行修改
    具体如何修改就通过钩子processProperties交由子类实现

  4. PlaceholderConfigurerSupport
    使用visitor设计模式,通过BeanDefinitionVisitor和StringValueResolver更新属性
    StringValueResolver是一个转化String类型数据的接口,真正更新属性的api实现竟然是在PropertyPlaceholderHelper#parseStringValue

  5. PropertySourcesPlaceholderConfigurer
    覆写postProcessorBeanFactory api定义解析流程


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

[html]  view plain  copy
  1. 只需要在spring的配置文件里添加一句:<context:property-placeholder?location="classpath:jdbc.properties"/>?即可,这里location值为参数配置文件的位置,参数配置文件通常放在src目录下,而参数配置文件的格式跟java通用的参数配置文件相同,即键值对的形式,例如:  
  2.   
  3. #jdbc配置  
  4.   
  5. test.jdbc.driverClassName=com.mysql.jdbc.Driver  
  6. test.jdbc.url=jdbc:mysql://localhost:3306/test  
  7. test.jdbc.username=root  
  8. 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
  1. <?xml version="1.0" encoding="UTF-8" ?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.        xmlns:context="http://www.springframework.org/schema/context"  
  5.        xmlns:p="http://www.springframework.org/schema/p"  
  6.        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd  
  7.        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">  
  8.    <context:property-placeholder location="classpath*:conf/conf_a.properties"/>  
  9.    <bean class="com.xxx.aaa.Bean1"  
  10.           p:driverClassName="${modulea.jdbc.driverClassName}"  
  11.           p:url="${modulea.jdbc.url}"  
  12.           p:username="${modulea.jdbc.username}"  
  13.           p:password="${modulea.jdbc.password}"/>  
  14. </beans>  
conf/conf_a.properties:
[html]  view plain  copy
  1. modulea.jdbc.driverClassName=com.mysql.jdbc.Driver  
  2. modulea.jdbc.username=cartan  
  3. modulea.jdbc.password=superman  
  4. modulea.jdbc.url=jdbc:mysql://127.0.0.1:3306/modulea?useUnicode=true&characterEncoding=utf8  


B模块的Spring配置文件如下:
[html]  view plain  copy
  1. <?xml version="1.0" encoding="UTF-8" ?>    
  2. <beans xmlns="http://www.springframework.org/schema/beans"    
  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    
  4.        xmlns:context="http://www.springframework.org/schema/context"    
  5.        xmlns:p="http://www.springframework.org/schema/p"    
  6.        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd    
  7.        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">    
  8.    <context:property-placeholder location="classpath*:conf/conf_b.properties"/>    
  9.    <bean class="com.xxx.bbb.Bean1"    
  10.           p:driverClassName="${moduleb.jdbc.driverClassName}"    
  11.           p:url="${moduleb.jdbc.url}"    
  12.           p:username="${moduleb.jdbc.username}"    
  13.           p:password="${moduleb.jdbc.password}"/>    
  14. </beans>    
conf/conf_b.properties:
[html]  view plain  copy
  1. moduleb.jdbc.driverClassName=com.mysql.jdbc.Driver  
  2. moduleb.jdbc.username=cartan  
  3. moduleb.jdbc.password=superman  
  4. 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
  1. <context:property-placeholder location="classpath*:conf/conf_b.properties"/>   

然后重新写个xml:

[html]  view plain  copy
  1. <?xml version="1.0" encoding="UTF-8" ?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.        xmlns:context="http://www.springframework.org/schema/context"  
  5.        xmlns:p="http://www.springframework.org/schema/p"  
  6.        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd  
  7.        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">  
  8.    <context:property-placeholder location="classpath*:conf/conf*.properties"/>  
  9.    <import resource="a.xml"/>  
  10.    <import resource="b.xml"/>  
  11. </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 的下载地址请点这里

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值