在上一篇解析自定义命名空间的标签中,我们已经知道解析自定义命名空间的标签需要用到NamespaceHandler接口的实现类,并且知道spring是如何获取命名空间对应的命名空间处理器对象的。因此我们很容易就能在spring-context包下的META-INF/spring.handlers文件中找到http://www.springframework.org/schema/context命名空间(即本文说的context命名空间)的处理器org.springframework.context.config.ContextNamespaceHandler,下面是ContextNamespaceHandler类的源码。

  1. public class ContextNamespaceHandler extends NamespaceHandlerSupport {
  2. @Override
  3. public void init() {
  4. registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
  5. registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
  6. registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
  7. registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
  8. registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
  9. registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
  10. registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
  11. registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
  12. }
  13. }

ContextNamespaceHandler 实现了NamespaceHandler接口的init方法来为context命名空间下的标签注册解析器BeanDefinitionParser对象。

Spring context命名空间有property-placeholder、property-override、annotation-config、component-scan、load-time-weaver、spring-configured、mbean-export和mbean-server 8个标签。这8个标签都有一个BeanDefinitionParser实现类与之对应。这一节我们分别探讨property-placeholder和property-override标签的解析。

解析property-placeholder标签


property-placeholder标签用于加载property属性文件。如果bean的<property>value值与属性文件中的某个值相同,那么,默认情况下,定义<property>的value值可以使用表达式“${key}”,其中key为属性文件中=左边的字符串。

property-placeholder标签对应的BeanDefinitionParser实现类是PropertyPlaceholderBeanDefinitionParser,下面是这个类的继承结构。 

AbstractBeanDefinitionParser是PropertyPlaceholderBeanDefinitionParser类的一个抽象父类,它实现了BeanDefinitionParser接口的parse(Element element, ParserContext parserContext)方法,代码如下。

  1. @Override
  2. public final BeanDefinition parse(Element element, ParserContext parserContext) {
  3. // 调用抽象方法parseInternal(Element element, ParserContext parserContext)
  4. // 这个方法有子类实现,把解析指定Element对象的任务交给子类完成
  5. AbstractBeanDefinition definition = parseInternal(element, parserContext);
  6. if (definition != null && !parserContext.isNested()) {
  7. try {
  8. // 解析并未bean生成一个id值
  9. String id = resolveId(element, definition, parserContext);
  10. if (!StringUtils.hasText(id)) {
  11. parserContext.getReaderContext().error(
  12. "Id is required for element '" + parserContext.getDelegate().getLocalName(element)
  13. + "' when used as a top-level tag", element);
  14. }
  15. String[] aliases = null;
  16. // 检测是否应该把name属性作为bean的别名,默认为true
  17. // 子类可以重写shouldParseNameAsAliases()来决定
  18. if (shouldParseNameAsAliases()) {
  19. // 获取bean的别名
  20. String name = element.getAttribute("name");
  21. if (StringUtils.hasLength(name)) {
  22. aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
  23. }
  24. }
  25. // 创建BeanDefinitionHolder对象
  26. BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
  27. // 注册BeanDefintion
  28. registerBeanDefinition(holder, parserContext.getRegistry());
  29. if (shouldFireEvents()) {
  30. BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
  31. postProcessComponentDefinition(componentDefinition);
  32. parserContext.registerComponent(componentDefinition);
  33. }
  34. } catch (BeanDefinitionStoreException ex) {
  35. parserContext.getReaderContext().error(ex.getMessage(), element);
  36. return null;
  37. }
  38. }
  39. return definition;
  40. }

AbstractBeanDefinitionParser的parse方法,首先把解析节点的任务交给子类来完成,子类需要实现parseInternal(Element element, ParserContext parserContext)方法并返回一个AbstractBeanDefinition 对象;然后根据需要设置bean的id和别名;最后创建并注册BeanDefinitionHolder对象。下面我们重点看parseInternal方法,在PropertyPlaceholderBeanDefinitionParser的继承体系中,parseInternal方法的实现在AbstractSingleBeanDefinitionParser类中,代码如下。

  1. @Override
  2. protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
  3. // 创建BeanDefinitionBuilder对象,这个对象只是代理了一个GenericBeanDefinition对象
  4. BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
  5. String parentName = getParentName(element);
  6. if (parentName != null) {
  7. builder.getRawBeanDefinition().setParentName(parentName);
  8. }
  9. // 获取要实例化bean的类对象,默认为null,一般由子类提供。
  10. Class<?> beanClass = getBeanClass(element);
  11. if (beanClass != null) {
  12. builder.getRawBeanDefinition().setBeanClass(beanClass);
  13. } else {
  14. String beanClassName = getBeanClassName(element);
  15. if (beanClassName != null) {
  16. builder.getRawBeanDefinition().setBeanClassName(beanClassName);
  17. }
  18. }
  19. builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
  20. if (parserContext.isNested()) {
  21. // 嵌套的bean定义,必须和外层的bean在同一个作用域
  22. builder.setScope(parserContext.getContainingBeanDefinition().getScope());
  23. }
  24. if (parserContext.isDefaultLazyInit()) {
  25. // 默认为延迟加载
  26. builder.setLazyInit(true);
  27. }
  28. // 把继续解析标签的任务交给子类
  29. doParse(element, parserContext, builder);
  30. return builder.getBeanDefinition();
  31. }

AbstractSingleBeanDefinitionParser的parseInternal方法创建了一个BeanDefinitionBuilder对象,这个对象只是代理了GenericBeanDefinition对象,以简化对GenericBeanDefinition对象的操作,然后就是一些基本的配置,这在代码中已经体现了。parseInternal也并未对节点做实质性的操作,它只调用为子类创建的一些钩子方法,这些方法有:

用于获取要实例化bean的类(Class)对象或者类全名称的方法:

  1. protected Class<?> getBeanClass(Element element) {
  2. return null;
  3. }
  4. protected String getBeanClassName(Element element) {
  5. return null;
  6. }

以及用于进一步解析节点的方法

  1. protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
  2. doParse(element, builder);
  3. }
  4. protected void doParse(Element element, BeanDefinitionBuilder builder) {
  5. }

property-placeholder标签的解析器PropertyPlaceholderBeanDefinitionParser类重写了上面2组方法中的getBeanClass(Element element)方法和doParse(Element element, BeanDefinitionBuilder builder) 方法。 
下面是getBeanClass(Element element)方法的源码。

  1. @Override
  2. protected Class<?> getBeanClass(Element element) {
  3. // 从spring3.1开始system-properties-mode属性的默认值就为ENVIRONMENT,不再是FALLBACK
  4. if ("ENVIRONMENT".equals(element.getAttribute("system-properties-mode"))) {
  5. // PropertySourcesPlaceholderConfigurer对象可以从上下文的环境对象中获取属性值
  6. return PropertySourcesPlaceholderConfigurer.class;
  7. }
  8. return PropertyPlaceholderConfigurer.class;
  9. }

说明:PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer都是抽象类PlaceholderConfigurerSupport的直接子类,见总结。

下面是doParse(Element element, BeanDefinitionBuilder builder)方法的源码。

  1. @Override
  2. protected void doParse(Element element, BeanDefinitionBuilder builder) {
  3. super.doParse(element, builder);
  4. builder.addPropertyValue("ignoreUnresolvablePlaceholders",
  5. Boolean.valueOf(element.getAttribute("ignore-unresolvable")));
  6. String systemPropertiesModeName = element.getAttribute("system-properties-mode");
  7. if (StringUtils.hasLength(systemPropertiesModeName) &&
  8. !systemPropertiesModeName.equals("system-properties-mode")) {
  9. builder.addPropertyValue("systemPropertiesModeName", "SYSTEM_PROPERTIES_MODE_" + systemPropertiesModeName);
  10. }
  11. // 指定一个分隔符用于分隔默认值,默认为英文冒号:
  12. // 比如${user.name:chyohn},如果user.name没有在属性文件中定义,则使用默认值chyohn
  13. // 假设设置的分隔符为英文?,则上面的定义应该为${name?choyhn}
  14. if (element.hasAttribute("value-separator")) {
  15. builder.addPropertyValue("valueSeparator", element.getAttribute("value-separator"));
  16. }
  17. // 设置是否允许trim获取到的属性值,默认为false
  18. if (element.hasAttribute("trim-values")) {
  19. builder.addPropertyValue("trimValues", element.getAttribute("trim-values"));
  20. }
  21. // 指定一个值用于表示null,比如指定的为hasNull,
  22. // 如果某一个属性值定义的时候为haveNull,那么生成的bean的这个属性真正值就是null
  23. if (element.hasAttribute("null-value")) {
  24. builder.addPropertyValue("nullValue", element.getAttribute("null-value"));
  25. }
  26. }

PropertyPlaceholderBeanDefinitionParser类的doParse方法首先调用父类AbstractPropertyLoadingBeanDefinitionParser的doParse方法,然后根据节点配置的属性值来修改PlaceholderConfigurerSupport的属性值。这里我们在继续看看AbstractPropertyLoadingBeanDefinitionParser的doParse方法源代码。

  1. @Override
  2. protected void doParse(Element element, BeanDefinitionBuilder builder) {
  3. // 获取属性文件的地址参数
  4. String location = element.getAttribute("location");
  5. if (StringUtils.hasLength(location)) {
  6. // 如果有多个属性文件,每个属性文件可以使用英文逗号隔开。
  7. String[] locations = StringUtils.commaDelimitedListToStringArray(location);
  8. builder.addPropertyValue("locations", locations);
  9. }
  10. // 获取指定的Properties对象的bean名称
  11. // 如果local-override属性为true,这里设置的properties将覆盖属性文件中的内容
  12. String propertiesRef = element.getAttribute("properties-ref");
  13. if (StringUtils.hasLength(propertiesRef)) {
  14. builder.addPropertyReference("properties", propertiesRef);
  15. }
  16. // 获取属性文件编码
  17. String fileEncoding = element.getAttribute("file-encoding");
  18. if (StringUtils.hasLength(fileEncoding)) {
  19. builder.addPropertyValue("fileEncoding", fileEncoding);
  20. }
  21. // 设置排序,即优先级
  22. String order = element.getAttribute("order");
  23. if (StringUtils.hasLength(order)) {
  24. builder.addPropertyValue("order", Integer.valueOf(order));
  25. }
  26. // 设置是否忽略指定的属性文件不存在的错误,true为是
  27. builder.addPropertyValue("ignoreResourceNotFound",
  28. Boolean.valueOf(element.getAttribute("ignore-resource-not-found")));
  29. // 一般来说,属性文件的内容更后加载。
  30. // 如果localOverride为true,那么PropertiesLoaderSupport的localProperties内容就会覆盖属性文件中相同key的的内容。
  31. // 如果标签是property-placeholder且localOverride为true,上下文的环境对象中的数据也会覆盖属性文件中相同key的内容。
  32. // 默认为false
  33. builder.addPropertyValue("localOverride",
  34. Boolean.valueOf(element.getAttribute("local-override")));
  35. builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  36. }

解析property-override标签


property-override标签的作用是为xml配置文件中的bean的属性指定最终结果。

property-override标签的解析器类为PropertyOverrideBeanDefinitionParser,这个类和property-placeholder标签的解析器类PropertyPlaceholderBeanDefinitionParser一样是AbstractPropertyLoadingBeanDefinitionParser类的直接子类,并且PropertyOverrideBeanDefinitionParser重写了getBeanClass(Element element)和doParse(Element element, BeanDefinitionBuilder builder)方法,代码如下。

  1. @Override
  2. protected Class<?> getBeanClass(Element element) {
  3. return PropertyOverrideConfigurer.class;
  4. }
  5. @Override
  6. protected void doParse(Element element, BeanDefinitionBuilder builder) {
  7. super.doParse(element, builder);
  8. // 设置忽略不正确的键,也就是忽略格式不正确的键,或者是.左边的bean名称不存在的键
  9. // 默认为false,表示不忽略
  10. builder.addPropertyValue("ignoreInvalidKeys",
  11. Boolean.valueOf(element.getAttribute("ignore-unresolvable")));
  12. }

关于AbstractPropertyLoadingBeanDefinitionParser类在前面已经探讨过了,关于这段代码也就没有什么要多说的。

总结


(1)property-override和property-placeholder的不同点。

  • 功能不一样,property-override标签的作用是为xml配置文件中的bean的属性指定最终结果;而property-placeholder标签的作用是把xml配置文件中bean 的<property>标签的value值替换成正真的值,而且<property>标签的value值必须符合特定的表达式格式,默认为“${key}”,其中key为属性文件中的key。

  • 属性文件内容要求不一样,property-override标签加载的properties文件中的key的格式有严格的要求,必须为“bean名称.bean属性”。如果属性ignore-unresolvable的值为false,那么属性文件中的bean名称必须在当前容器中能找到对应的bean。

(2)property-override和property-placeholder的共同点。

  • 两者都是以properties文件作为数据来源。

  • 两者的解析器BeanDefinitionParser类都继承自AbstractPropertyLoadingBeanDefinitionParser类。因此它们共有AbstractPropertyLoadingBeanDefinitionParser及其父类中所处理的标签属性,并且这些属性在两个标签中具有相同的作用。这其实都归于它们所代表的工厂后处理器都继承了PropertiesLoaderSupport类,具体看下面的继承结构图,其中第一个是property-override的,后面两个是property-placeholder的。 
     
     

 
(原文地址:http://blog.csdn.net/chyohn/article/details/54945777?ref=myread)

[转] Spring4.3.x 浅析xml配置的解析过程(6)——解析context命名空间之property-placeholder和property-override标签的更多相关文章

  1. Spring3.2 中 Bean 定义之基于 XML 配置方式的源码解析

    Spring3.2 中 Bean 定义之基于 XML 配置方式的源码解析 本文简要介绍了基于 Spring 的 web project 的启动流程,详细分析了 Spring 框架将开发人员基于 XML ...

  2. spring4、hibernate4整合xml配置

    <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...

  3. build.xml配置编译打包过程(转)

    工程目录如下,使用eclipse中的ant对此工程进行编译打包: MonServer | --------src |            |--------com |            |--- ...

  4. Spring、Spring事务详解;使用XML配置事务

    @Transactional可以设置以下参数: @Transactional(readOnly=false) // 指定事务是否只读的 true/false @Transactional(rollba ...

  5. 基于xml配置springmvc

    controller关键代码 public class MenuController extends MultiActionController 方法: public ModelAndView lis ...

  6. spring 5.x 系列第5篇 —— 整合 mybatis + druid 连接池 (xml配置方式)

    源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all 项目目录结构 1.创建maven工程,除了Spring基本依赖外,还需要导 ...

  7. 从底层源码浅析Mybatis的SqlSessionFactory初始化过程

    目录 搭建源码环境 POM依赖 测试SQL Mybatis全局配置文件 UserMapper接口 UserMapper配置 User实体 Main方法 快速进入Debug跟踪 源码分析准备 源码分析 ...

  8. 丢弃重口味的xml配置--spring4用groovy配置bean(转)

    spring4之前,bean的配置可以主要分为两种方式,一种是使用基于xml,个人非常讨厌这种方式,因为明明一件很简单的事,放在xml中就会多了不少繁杂的信息.另一种方式,是从spring3.0开始, ...

  9. Spring4.0编程式定时任务配置

    看过很多定时调度的配置,大多使用XML配置,觉得比较麻烦,也比较老套.这里介绍一种基于spring4.0注解编程式配置定时任务,简单清晰,使用方便.. 至于引入spring相关jar这里不多说,直接切 ...

随机推荐

  1. 【linux】Ubuntu中shell脚本无法使用source的原因及解决方法

    问题现象: shell脚本中source aaa.sh时提示 source: not found 原因: ls -l `which sh` 提示/bin/sh -> dash 这说明是用dash ...

  2. vsphere产品下载列表

    https://my.vmware.com/cn/web/vmware/info/slug/datacenter_cloud_infrastructure/vmware_vsphere_with_op ...

  3. POI3.10读取Excel模板填充数据后生成新的Excel文件

    private final DecimalFormat df = new DecimalFormat("#0.00"); public void test(){ String fi ...

  4. TQ2440与西门子S7-200 PLC自由口通信实现过程中问题总结

    1.在win7上安装好PLC编程软件 STEP 7 MicroWIN 之后,无法实现编程软件与PLC的通信连接? 原因:STEP 7 MicroWIN 对win7支持不是很好 解决办法:在win7中安 ...

  5. 使用Gulp

    为什么要使用Gulp 在前端开发中通常须要做,预处理语言的编译.js文件的压缩.css文件的压缩.图片的压缩等一系列工作,而使用Gulp能够自己主动化的完毕这些工作,从而提高站点的开发效率,在我的博客 ...

  6. emplace_back() 和 push_back 的区别(转)

    在引入右值引用,转移构造函数,转移复制运算符之前,通常使用push_back()向容器中加入一个右值元素(临时对象)的时候,首先会调用构造函数构造这个临时对象,然后需要调用拷贝构造函数将这个临时对象放 ...

  7. Linux中wget用法

    Wget简介:Linux系统中wget是一个下载文件的工具,它用在命令行下.对于Linux用户是必不可少的工具,我们经常要下载一些软件或从远程服务器恢复备份到本地服务器.wget支持HTTP,HTTP ...

  8. CTRL+SHIFT

    CTRL+SHIFT+鼠标左右,上下拖动,可快速实现平行和垂直上下复制的功能,

  9. <c:url>标签相关知识点

    <c:url>标签: value:指定路径!他会在路径前面自动添加项目名. <c:url value="/index.jsp"/>,他会输出/day14/i ...

  10. 纪念品分组(NOIP2007)

    纪念品分组(NOIP2007)[题目描述] 元旦快到了,校学生会让乐乐负责新年晚会的纪念品发放工作. 为使得参加晚会的同学所获得的纪念品价值相对均衡,他要把 购来的纪念品根据价格进行分组,但每组最多只 ...