Spring IOC源码:obtainFreshBeanFactory 详解(下)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Spring IOC源码:obtainFreshBeanFactory 详解(下)

文章目录

Spring源码系列:

前言

正文

方法1:parseCustomElement

方法2:getNamespaceHandlerResolver().resolve

方法3:getHandlerMappings

方法4:namespaceHandler.init()

方法5:handler.parse

自定义命名空间

1、新建自定义命名空间处理器

2、新建自定义命名空间解析器

3、新建spring.handlers文件

4、新建spring.schemas文件

5、新建xsd文件,声明标签规范

6、新建xml文件配置自定义标签

6、测试用例

总结

Spring源码系列:

Spring IOC源码:简单易懂的Spring IOC 思路介绍

Spring IOC源码:核心流程介绍

Spring IOC源码:ApplicationContext刷新前准备工作

Spring IOC源码:obtainFreshBeanFactory 详解(上)

Spring IOC源码:obtainFreshBeanFactory 详解(中)

Spring IOC源码:obtainFreshBeanFactory 详解(下)

Spring IOC源码:<context:component-scan>源码详解

Spring IOC源码:invokeBeanFactoryPostProcessors 后置处理器详解

Spring IOC源码:registerBeanPostProcessors 详解

Spring IOC源码:实例化前的准备工作

Spring IOC源码:finishBeanFactoryInitialization详解

Spring IoC源码:getBean 详解

Spring IoC源码:createBean( 上)

Spring IoC源码:createBean( 中)

Spring IoC源码:createBean( 下)

Spring IoC源码:finishRefresh 完成刷新详解

前言

前面两篇文章介绍了BeanDefinition的解析,以及默认命名空间的解析过程,这节介绍obtainFreshBeanFactory中另一个核心的解析逻辑,自定义命名空间的解析。

正文

自定义命名空间解析,入口方法为delegate.parseCustomElement(ele),见方法1详解

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);
    }
  }

方法1:parseCustomElement

  public BeanDefinition parseCustomElement(Element ele) {
    return parseCustomElement(ele, null);
  }
  /**
   * Parse a custom element (outside of the default namespace).
   * @param ele the element to parse
   * @param containingBd the containing bean definition (if any)
   * @return the resulting bean definition
   */
  @Nullable
  public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    //获取命名空间路径 例如:http://www.springframework.org/schema/aop
    String namespaceUri = getNamespaceURI(ele);
    if (namespaceUri == null) {
      return null;
    }
    //通过命名空间解析器加载命名空间处理器
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
      error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
      return null;
    }
    //调用解析
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
  }

this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri)方法,见方法3详解

handler.parse(ele, new ParserContext(this.readerContext, this, containingBd))方法,见方法5详解

方法2:getNamespaceHandlerResolver().resolve

  public NamespaceHandler resolve(String namespaceUri) {
    //获取命名空间集合
    Map<String, Object> handlerMappings = getHandlerMappings();
    //根据命名空间路径获取
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {
      return null;
    }
    else if (handlerOrClassName instanceof NamespaceHandler) {
      return (NamespaceHandler) handlerOrClassName;
    }
    else {
      //如果是未实例化的类路径,转为String
      String className = (String) handlerOrClassName;
      try {
        Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
        if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
          throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
              "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
        }
        //通过反射创建对象
        NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
        //调用处理器初始化方法
        namespaceHandler.init();
        //加入集合缓存中
        handlerMappings.put(namespaceUri, namespaceHandler);
        return namespaceHandler;
      }
      catch (ClassNotFoundException ex) {
        throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
            "] for namespace [" + namespaceUri + "]", ex);
      }
      catch (LinkageError err) {
        throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
            className + "] for namespace [" + namespaceUri + "]", err);
      }
    }
  }

getHandlerMappings(),见方法3详解

namespaceHandler.init(),见方法4详解

方法3:getHandlerMappings

private Map<String, Object> getHandlerMappings() {
    //获取当前集合中的处理器
    Map<String, Object> handlerMappings = this.handlerMappings;
    if (handlerMappings == null) {
      synchronized (this) {
        handlerMappings = this.handlerMappings;
        if (handlerMappings == null) {
          if (logger.isTraceEnabled()) {
            logger.trace("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
          }
          try {
            //根据处理器路径进行查找加载
            Properties mappings =
                PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
            if (logger.isTraceEnabled()) {
              logger.trace("Loaded NamespaceHandler mappings: " + mappings);
            }
            handlerMappings = new ConcurrentHashMap<>(mappings.size());
            CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
            this.handlerMappings = handlerMappings;
          }
          catch (IOException ex) {
            throw new IllegalStateException(
                "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
          }
        }
      }
    }
    return handlerMappings;
  }

handlerMappings集合中的处理器如下图:

方法4:namespaceHandler.init()

因为当前配置文件中的自定义命名空间是AOP,所以这里会进入AOP处理器的init方法。

  public void init() {
    //这里主要注册需要用到的解析器
    // In 2.0 XSD as well as in 2.1 XSD.
    registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
    registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
    registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
    // Only in 2.0 XSD: moved to context namespace as of 2.1
    registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
  }

方法5:handler.parse

进入方法1中的handler.parse(ele, new ParserContext(this.readerContext, this, containingBd))方法;

  public BeanDefinition parse(Element element, ParserContext parserContext) {
    //根据当前节点的标签去查找对应的解析器
    BeanDefinitionParser parser = findParserForElement(element, parserContext);
    //调用解析器处理逻辑
    return (parser != null ? parser.parse(element, parserContext) : null);
  }
  /**
   * Locates the {@link BeanDefinitionParser} from the register implementations using
   * the local name of the supplied {@link Element}.
   */
  @Nullable
  private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    //获取节点标签  如:config
    String localName = parserContext.getDelegate().getLocalName(element);
    //根据名称获取解析器
    BeanDefinitionParser parser = this.parsers.get(localName);
    if (parser == null) {
      parserContext.getReaderContext().fatal(
          "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
    }
    return parser;
  }

到此整个obtainFreshBeanFactory 的讲解就结束了,自定义命名空间处理器及其解析方法,后续文章中会跟案例进行讲解。

自定义命名空间

看完上面的步骤,大家应该对自定义命名空间过程有一定的理解,下面我们自定义案例来加深理解,我们需要按以下步骤来完成Demo。

1、新建自定义命名空间处理器,一个处理器可以添加多个解析器

2、新建自定义命名空间解析器,编写解析处理逻辑

3、新建spring.handlers文件,按Key-Value格式指定处理器路径

4、新建spring.schemas文件,按Key-Value格式指定xsd自定义标签文件路径

5、新建xsd文件,声明标签规范,指定命名空间处理器

6、新建xml文件配置自定义标签,完成类的管理。

1、新建自定义命名空间处理器

我们新建一个空模块Gradle空模块

编辑build.gradle文件,引入spring 包

新建处理器类 ZdcHandler

package com.zhudachang.namespace;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class ZdcHandler extends NamespaceHandlerSupport {
  @Override
  public void init() {
    super.registerBeanDefinitionParser("zdc",new ZdcParser());
  }
}

2、新建自定义命名空间解析器

package com.zhudachang.namespace;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
/**
 * Zdc解析器
 */
public class ZdcParser implements BeanDefinitionParser {
  @Override
  public BeanDefinition parse(Element element, ParserContext parserContext) {
    RootBeanDefinition beanDefinition = new RootBeanDefinition();
    beanDefinition.setBeanClass(ZdcDomain.class);
    //声明PropertyValues对象
    MutablePropertyValues mutablePropertyValues = beanDefinition.getPropertyValues();
    // 添加name属性
    if (element.hasAttribute("name")) {
      mutablePropertyValues.addPropertyValue("name", element.getAttribute("name"));
    }
    // 解析添加remark属性
    if (element.hasAttribute("remark")) {
      mutablePropertyValues.addPropertyValue("remark", element.getAttribute("remark"));
    }
    String id = element.getAttribute("id");
    // 拿到注册表, 注册BeanDefinition
    parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
    return beanDefinition;
  }
}

新建实体类

package com.zhudachang.namespace;
public class ZdcDomain {
  String name;
  String remark;
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public String getRemark() {
    return remark;
  }
  public void setRemark(String remark) {
    this.remark = remark;
  }
}

3、新建spring.handlers文件

resource目录下创建META-INF目录,并创建spring.handlers文件,指定空间路径及其对应的处理器类路径

http\://com.zhudachang.namespace/schema/zdc=com.zhudachang.namespace.ZdcHandler

4、新建spring.schemas文件

resource目录下创建META-INF目录,并创建spring.schemas文件,指定路径及其对应xsd文件路径

http\://com.zhudachang.namespace/schema/zdc/zdc-1.0.xsd=./com/zdc/spancename/zdc/config/zdc-1.0.xsd

5、新建xsd文件,声明标签规范

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://com.zhudachang.namespace/schema/zdc"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema"
      targetNamespace="http://com.zhudachang.namespace/schema/zdc"
      elementFormDefault="qualified">
  <xsd:element name="zdc" >
    <xsd:complexType>
      <xsd:attribute name="id" type="xsd:string"/>
      <xsd:attribute name="name" type="xsd:string"/>
      <xsd:attribute name="remark" type="xsd:string"/>
    </xsd:complexType>
  </xsd:element>
</xsd:schema>

6、新建xml文件配置自定义标签

如果你的测试模块跟上述定义解析器标签不在同个模块下,记得先引入模块。

<?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:zdc="http://com.zhudachang.namespace/schema/zdc"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://com.zhudachang.namespace/schema/zdc
       http://com.zhudachang.namespace/schema/zdc/zdc-1.0.xsd">
    <zdc:zdc id="zdc" name="猪大肠" remark="猪大肠备注"></zdc:zdc>
</beans>

6、测试用例

编写测试用例

  public static void main(String[] args) {
    ClassPathXmlApplicationContext applicationContext=new ClassPathXmlApplicationContext("application-namespace.xml");
    ZdcDomain zdcDomain = (ZdcDomain) applicationContext.getBean("zdc");
    System.out.println(zdcDomain.getName());
  }

测试结果:

总结

本篇文章介绍了自定义命名空间的解析过程,并且编写的自定义命名空间案例加深理解。后续文章中会拿常用的自定义标签context:component-scan 进行讲解。

目录
相关文章
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
109 2
|
22天前
|
XML Java 数据格式
【SpringFramework】Spring IoC-基于XML的实现
本文主要讲解SpringFramework中IoC和DI相关概念,及基于XML的实现方式。
108 69
|
20天前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
49 21
|
27天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
12天前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
25天前
|
存储 Java 应用服务中间件
【Spring】IoC和DI,控制反转,Bean对象的获取方式
IoC,DI,控制反转容器,Bean的基本常识,类注解@Controller,获取Bean对象的常用三种方式
|
1月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
63 2
|
8月前
|
Java 关系型数据库 数据库连接
Spring源码解析--深入Spring事务原理
本文将带领大家领略Spring事务的风采,Spring事务是我们在日常开发中经常会遇到的,也是各种大小面试中的高频题,希望通过本文,能让大家对Spring事务有个深入的了解,无论开发还是面试,都不会让Spring事务成为拦路虎。
112 1
|
3月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
214 5
|
3月前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)