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配置文件.

 protected final void refreshBeanFactory() 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

 protected void parseBeanDefinitions(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
 public void init() {
  // 这就是我们要找的解析器
  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解析

  1. 生成bean id,使用BeanNameGenerator生成,或者直接读取id属性
  2. 处理name 与别名aliases
  3. 往容器中注册bean
  4. 进行事件触发

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实例化

protected void invokeBeanFactoryPostProcessors(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定义解析流程

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

您可能感兴趣的文章:

  • Spring整合Mybatis使用<context:property-placeholder>时的坑
(0)

相关推荐

  • Spring整合Mybatis使用<context:property-placeholder>时的坑

    背景 最近项目要上线,需要开发一个数据迁移程序.程序的主要功能就是将一个数据库里的数据,查询出来经过一系列处理后导入另一个数据库.考虑到开发的方便快捷.自然想到用spring和mybatis整合一下.甚至用mybatis的自动代码生成,可以省下大量dao层的开发. 整合的坑 之前的项目:以前也有过这种类似的程序,就把spring和mybatis整合的配置直接拿来修改下用.之前的整合配置是这样子的: 1.考虑到数据库url.用户名密码的可配置性,将这些信息放入properties文件.在sprin

  • Spring中Bean的三种实例化方式详解

    目录 一.环境准备 二.构造方法实例化 三.分析Spring的错误信息 四.静态工厂实例化 4.1 工厂方式创建bean 4.2 静态工厂实例化 五.实例工厂与FactoryBean 5.1 环境准备 5.2 实例工厂实例化 5.3 FactoryBean的使用 六.bean实例化小结 一.环境准备 准备开发环境 创建一个Maven项目 pom.xml添加依赖 resources下添加spring的配置文件applicationContext.xml 最终项目的结构如下: 二.构造方法实例化 在

  • Spring中事务用法示例及实现原理详解

    前言 Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现. 关于事务,简单来说,就是为了保证数据完整性而存在的一种工具,其主要有四大特性:原子性,一致性,隔离性和持久性.对于Spring事务,其最终还是在数据库层面实现的,而Spring只是以一种比较优雅的方式对其进行封装支持.本文首先会通过一个简单的示例来讲解Spring事务是如何使用的,然后会讲解Spring是如何解析xml中的标签,并对事

  • 如何在Spring中使用编码方式动态配置Bean详解

    bean与spring容器的关系 Bean配置信息定义了Bean的实现及依赖关系,Spring容器根据各种形式的Bean配置信息在容器内部建立Bean定义注册表,然后根据注册表加载.实例化Bean,并建立Bean和Bean的依赖关系,最后将这些准备就绪的Bean放到Bean缓存池中,以供外层的应用程序进行调用. 本文将给大家详细介绍关于在Spring中使用编码方式动态配置Bean的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 1 DefaultListableBea

  • Spring中BeanFactory与FactoryBean接口的区别详解

    前言 Spring框架中的BeanFactory接口和FactoryBean接口因为名称相似,老是容易搞混淆,而且也是面试过程中经常会碰到的一个问题.所以本文就专门给大家整理出来. 一.BeanFactory接口 BeanFactory接口是Spring容器的核心接口,负责:实例化.定位.配置应用程序中的对象及建立这些对象间的依赖. Spring为我们提供了许多易用的BeanFactory实现,XmlBeanFactory就是常用的一个,该实现将以XML方式描述组成应用的对象及对象间的依赖关系.

  • C#中Property和Attribute的区别实例详解

    本文实例分析了C#中Property和Attribute的区别.分享给大家供大家参考.具体分析如下: 在C#中有两个属性,分别为Property和Attribute,两个的中文意思都有特性.属性之间,但是用法上却不一样,为了区别,本文暂把Property称为特性,把Attribute称为属性. Attribute才是本文的主角,把它称为属性我觉得很恰当.属性的意思就是附属于某种事物上的,用来说明这个事物的各种特征的一种描述.而Attribute就是干这事的.它允许你将信息与你定义的C#类型相关联

  • Spring中Bean的作用域与生命周期详解

    目录 一.Bean的作用域 1.单实例Bean声明 2.多实例Bean声明 二.Bean的生命周期 1.bean的初始和销毁 2.bean的后置处理器 总结 一.Bean的作用域 首先我们来讲一下有关于bean的作用域, 一般情况下,我们书写在IOC容器中的配置信息,会在我们的IOC容器运行时被创建,这就导致我们通过IOC容器获取到bean对象的时候,往往都是获取到了单实例的Bean对象, 这样就意味着无论我们使用多少个getBean()方法,获取到的同一个JavaBean都是同一个对象,这就是

  • Spring中xml配置文件的基础使用方式详解

    目录 1. xml配置文件的读取 1.1 通过类路径读取配置文件 1.2 通过文件系统绝对路径读取配置文件 1.3使用BeanFactory接口读取配置文件 2.带参构造对象的创建(constructor-arg标签) 3.使用另一个类中的方法创建对象,并放到Spring容器中 4.调用另一个类中的静态方法创建对象,并放到Spring容器中 5.对象的生命周期 6.单例多例的测试 1. xml配置文件的读取 目录结构 applicationContext.xml配置文件 <?xml versio

  • Spring中实例化bean的四种方式详解

    前言 在介绍Bean的实例化的方式之前,我们首先需要介绍一下什么是Bean,以及Bean的配置方式. 如果把Spring看作一个大型工厂,那么Spring容器中的Bean就是该工厂的产品.要想使用Spring工厂生产和管理Bean,就需要在配置文件中指明需要哪些Bean,以及需要使用何种方式将这些Bean装配到一起. Spring容器支持两种格式的配置文件,分别为Properties文件格式和xml文件格式,而在实际的开发当中,最常使用的额是xml文件格式,因此在如下的讲解中,我们以xml文件格

  • iOS中Block的回调使用和解析详解

    Block 回调实现 先跟着我实现最简单的 Block 回调传参的使用,如果你能举一反三,基本上可以满足了 OC 中的开发需求.已经实现的同学可以跳到下一节. 首先解释一下我们例子要实现什么功能(其实是烂大街又最形象的例子): 有两个视图控制器 A 和 B,现在点击 A 上的按钮跳转到视图 B ,并在 B 中的textfield 输入字符串,点击 B 中的跳转按钮跳转回 A ,并将之前输入的字符串 显示在 A 中的 label 上.也就是说 A 视图中需要回调 B 视图中的数据. 想不明白的同学

  • spring中aop的xml配置方法实例详解

    前言 AOP:即面向切面编程,是一种编程思想,OOP的延续.在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等等. aop,面向切面编程的目标就是分离关注点,比如:一个骑士只需要关注守护安全,或者远征,而骑士辉煌一生的事迹由谁来记录和歌颂呢,当然不会是自己了,这个完全可以由诗人去歌颂,比如当骑士出征的时候诗人可以去欢送,当骑士英勇牺牲的时候,诗人可以写诗歌颂骑士的一生.那么骑士只需要关注怎么打仗就好了.而诗人也只需要关注写诗歌颂和欢送就好了,那么这样就把功能分离了.所以可以把诗

随机推荐