Spring IoC源码学习:parseCustomElement 详解

简介: 我们通过 Spring IoC:parseDefaultElement详解 解析了默认命名空间节点的解析,本文将解析自定义命名空间节点的解析。

目录

Spring IoC源码学习全系列

前言

正文

parseBeanDefinitions方法

parseCustomElement

代码块1DefaultNamespaceHandlerResolver.resolve

代码块2getHandlerMappings

代码块3namespaceHandler#init

代码块4registerBeanDefinitionParser

代码块5parse

自定义一个命名空间

使用自定义命名空间

相关文章


Spring IoC源码学习全系列


小白也看得懂的 Spring IoC 核心流程介绍

Spring IoC源码学习:总览

Spring IoC源码学习ApplicationContext 刷新前的配置

Spring IoC源码学习obtainFreshBeanFactory详解

Spring IoC源码学习parseDefaultElement详解

Spring IoC源码学习parseCustomElement详解

Spring IoC源码学习:context:component-scan 节点详解

Spring IoC源码学习invokeBeanFactoryPostProcessors详解

Spring IoC源码学习registerBeanPostProcessors详解

Spring IoC源码学习finishBeanFactoryInitialization详解

Spring IoC源码学习getBean详解

Spring IoC源码学习createBean详解(上)

Spring IoC源码学习createBean详解(下)

Spring IoC源码学习:@Autowire 详解

Spring IoC源码学习:finishRefresh 详解

 

前言


我们通过Spring IoCparseDefaultElement详解解析了默认命名空间节点的解析,本文将解析自定义命名空间节点的解析。

 

正文


首先让我们回到Spring IoCobtainFreshBeanFactory详解文末的parseBeanDefinitions 方法。


parseBeanDefinitions方法

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    // 1.默认命名空间的处理
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        // 遍历root的子节点列表
        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)) {
                    // 1.1 默认命名空间节点的处理,例如: <bean id="test" class="" />
                    parseDefaultElement(ele, delegate);
                }
                else {
                    // 1.2 自定义命名空间节点的处理,例如:<context:component-scan/>、<aop:aspectj-autoproxy/>
                    delegate.parseCustomElement(ele);
                }
            }
        }
    } else {
        // 2.自定义命名空间的处理
        delegate.parseCustomElement(root);
    }
}

1.2 自定义命名空间节点的处理,见下面 parseCustomElement 方法。

 

parseCustomElement


public BeanDefinition parseCustomElement(Element ele) {
    return parseCustomElement(ele, null);
}
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
    // 1.拿到节点ele的命名空间,例如常见的:
    // <context> 节点对应命名空间: http://www.springframework.org/schema/context
    // <aop> 节点对应命名空间: http://www.springframework.org/schema/aop
    String namespaceUri = getNamespaceURI(ele);
    // 2.拿到命名空间对应的的handler, 例如:http://www.springframework.org/schema/context 对应 ContextNameSpaceHandler
    // 2.1 getNamespaceHandlerResolver: 拿到namespaceHandlerResolver
    // 2.2 resolve: 使用namespaceHandlerResolver解析namespaceUri, 拿到namespaceUri对应的NamespaceHandler
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
        return null;
    }
    // 3.使用拿到的handler解析节点(ParserContext用于存放解析需要的一些上下文信息)
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

2.1 Spring IoCobtainFreshBeanFactory详解中的代码块10,我们构建 XmlReaderContext 时会创建一个默认的DefaultNamespaceHandlerResolver,这边拿到的就是当时创建的DefaultNamespaceHandlerResolver 对象,例如下图。

image.png

2.2 使用 DefaultNamespaceHandlerResolver 解析namespaceUri,拿到对应的 handler见代码块1详解

3.使用拿到的 handler 解析 ele 节点,见代码块5详解

 

代码块1DefaultNamespaceHandlerResolver.resolve

@Override
public NamespaceHandler resolve(String namespaceUri) {
    // 1.拿到配置文件的所有命名空间和对应的handler
    // 例如:"http://www.springframework.org/schema/aop" -> "org.springframework.aop.config.AopNamespaceHandler"
    Map<String, Object> handlerMappings = getHandlerMappings();
    // 2.拿到当前命名空间对应的handler (可能是handler的className,也可能是已经实例化的handler)
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {
        // 2.1 如果不存在namespaceUri对应的handler,则返回null
        return null;
    }
    else if (handlerOrClassName instanceof NamespaceHandler) {
        // 2.2 如果是已经实例化的handler,则直接强转返回
        return (NamespaceHandler) handlerOrClassName;
    }
    else {
        // 2.3 如果是handler的className
        String className = (String) handlerOrClassName;
        try {
            // 2.3.1 根据className,使用类加载器拿到该类
            Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
            // 2.3.2 校验是否是继承自NamespaceHandler(所有的handler都继承自NamespaceHandler)
            if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
                        "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
            }
            // 2.3.3 使用无参构造函数实例化handlerClass类
            NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
            // 2.3.4 调用handler类的初始化方法(将命名空间下的节点名和对应的解析器注册到parsers缓存中)
            namespaceHandler.init();
            // 2.3.5 将实例化的handler放到缓存,替换原来的className
            // 原来为: namespaceUri -> handler的className,会被覆盖成: namespaceUri -> 实例化的handler
            handlerMappings.put(namespaceUri, namespaceHandler);
            // 返回实例化后的handler对象
            return namespaceHandler;
        }
        catch (ClassNotFoundException ex) {
            throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
                    namespaceUri + "] not found", ex);
        }
        catch (LinkageError err) {
            throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
                    namespaceUri + "]: problem with handler class file or dependent class", err);
        }
    }
}

1.拿到配置文件的所有命名空间和对应的 handler见代码块2详解

2.3.4 调用 handler 类的初始化方法,见代码块3详解

 

代码块2getHandlerMappings


private Map<String, Object> getHandlerMappings() {
    // 1.如果handlerMappings已经加载过,则直接返回
    if (this.handlerMappings == null) {
        synchronized (this) {
            // 2.如果handlerMappings还没加载过,则进行加载
            if (this.handlerMappings == null) {
                try {
                    // 2.1 使用给定的类加载器从指定的类路径资源加载所有属性
                    Properties mappings =
                            PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Loaded NamespaceHandler mappings: " + mappings);
                    }
                    Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
                    // 2.2 将Properties转换成Map, mappings -> handlerMappings
                    CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
                    // 2.3 将加载到的所有命名空间映射放到缓存
                    this.handlerMappings = handlerMappings;
                }
                catch (IOException ex) {
                    throw new IllegalStateException(
                            "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
                }
            }
        }
    }
    return this.handlerMappings;
}

2.1 通过Spring IoCobtainFreshBeanFactory详解中的代码块10,我们知道 handlerMappingsLocation 的默认值为“META-INF/spring.handlers”,因此在这边会使用指定的 classLoader 从所有类路径资源(META-INF/spring.handlers)加载所有属性,并使用 Properties 来存放spring.handlers 文件中的内容(命名空间和 handler 的键值对,例如下图)。

image.png

2.2 Properties 转成Map。其中 key 为命名空间,例如:http://www.springframework.org/schema/contextvalue为命名空间对应的 handler,例如:org.springframework.context.config.ContextNamespaceHandler,所有的handler都需要自己实现。


2.3 最后将转换后的 handlerMappings 放到缓存。

 

代码块3namespaceHandler#init


点击该方法,我们可以看到有很多个实现类,分别对应了不同的命名空间 Handler

image.png

我们拿最常见的命名空间:http://www.springframework.org/schema/context 来举例,对应的 handler 为 ContextNamespaceHandler。

image.png

内容很简单,就是给 context 命名空间下的不同节点指定了不同的 BeanDefinition 解析器,并将节点名和对应的解析器注册到缓存中,见代码块4详解。例如,最常用的 component-scan 对应 ComponentScanBeanDefinitionParser


 

代码块4registerBeanDefinitionParser


private final Map<String, BeanDefinitionParser> parsers =
        new HashMap<String, BeanDefinitionParser>();
// ... ...
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
    this.parsers.put(elementName, parser);
}

将节点名和对应的解析器放到 parsers 缓存中。

 

代码块5parse

public BeanDefinition parse(Element element, ParserContext parserContext) {
    // 1.findParserForElement: 给element寻找对应的BeanDefinition解析器
    // 2.使用BeanDefinition解析器解析element节点
    return findParserForElement(element, parserContext).parse(element, parserContext);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    // 1.1 拿到节点的localName,例如:annotation-config、component-scan
    String localName = parserContext.getDelegate().getLocalName(element);
    // 1.2 从parsers缓存中,拿到localName对应的解析器, 例如: component-scan -> ComponentScanBeanDefinitionParser
    BeanDefinitionParser parser = this.parsers.get(localName);
    if (parser == null) {
        parserContext.getReaderContext().fatal(
                "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
    }
    return parser;
}

1. parsers 缓存中(见代码块4),查找 element 节点对应的BeanDefinition 解析器。parsers缓存如下:

image.png

2.使用拿到的 BeanDefinition 解析器解析 element 节点。例如:使用 ComponentScanBeanDefinitionParser 解析器解析component-scan 节点。

到此为此,自定义命名空间的解析操作基本完成,只剩下具体解析器解析对应节点的内容没有写。


自定义命名空间节点常见的有:<context:component-scan /><context:annotation-config /><aop:aspectj-autoproxy/><tx:annotation-driven /> 等,下一篇文章将对最常见的 <context:component-scan /> 进行解析

 

自定义一个命名空间


看完上面的流程,我们可以参考 Spring 的自定义命名空间,尝试写一个自己的命名空间。


1.新建一个空项目,在 resources/META-INF 目录下新建一个 spring.handler 文件,内容如下:

http\://open.joonwhee.com/schema/dog=com.joonwhee.open.annotation.spring.DogNamespaceHandler

文件内容为一个键值对。

key 为自定义命名空间:http://open.joonwhee.com/schema/dog

value 为自定义命名空间对应的NameSpaceHandler com.joonwhee.open.annotation.spring.DogNamespaceHandler

 

2. resources/META-INF 目录下新建一个 spring.schemas 文件,内容如下:

http\://open.joonwhee.com/schema/dog/dog-

1.0.xsd=./com/joonwhee/open/dog/config/dog-1.0.xsd

文件内容为一个键值对。

key schema Urlhttp://open.joonwhee.com/schema/dog/dog-1.0.xsd

value 为本地 schema 路径:./com/joonwhee/open/dog/config/dog-1.0.xsd

 

3.对应第1 spring.handler 内容中的 value,在com.joonwhee.open.annotation.spring 目录下新建 DogNamespaceHandler.java,内容如下:

package com.joonwhee.open.annotation.spring;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
 * @author joonwhee
 * @date 2019/2/16
 */
public class DogNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        // 注册一个自定义的解析器, 用于解析命名空间下的annotation节点
        registerBeanDefinitionParser("annotation", new DogAnnotationDefinitionParser());
    }
}

新建 DogAnnotationDefinitionParser.java,内容如下:

package com.joonwhee.open.annotation.spring;
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;
/**
 * @author joonwhee
 * @date 2019/2/16
 */
public class DogAnnotationDefinitionParser implements BeanDefinitionParser {
    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(DogBean.class);
        MutablePropertyValues mutablePropertyValues = beanDefinition.getPropertyValues();
        // 添加name属性
        if (element.hasAttribute("name")) {
            mutablePropertyValues.addPropertyValue("name", element.getAttribute("name"));
        }
        // 添加package属性
        if (element.hasAttribute("package")) {
            mutablePropertyValues.addPropertyValue("package", element.getAttribute("package"));
        }
        String id = element.getAttribute("id");
        // 拿到注册表, 注册BeanDefinition
        parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
        return beanDefinition;
    }
}

其中用到的DogBean 代码如下:


package com.joonwhee.open.annotation.spring;
/**
 * @author joonwhee
 * @date 2019/2/16
 */
public class DogBean {
    private String name;
    private String basePackage;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPackage() {
        return basePackage;
    }
    public void setPackage(String basePackage) {
        this.basePackage = basePackage;
    }
    @Override
    public String toString() {
        return "DogBean{" +
                "name='" + name + '\'' +
                ", basePackage='" + basePackage + '\'' +
                '}';
    }
}

 

4.对应第2 spring.schemas 内容中的 value,在 resource下新建目录:com.joonwhee.open.dog.config,在目下新建文件 dog-1.0.xsd,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://open.joonwhee.com/schema/dog"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://open.joonwhee.com/schema/dog"
            elementFormDefault="qualified">
    <xsd:complexType name="annotationType">
        <xsd:attribute name="id" type="xsd:ID">
            <xsd:annotation>
                <xsd:documentation><![CDATA[ The unique identifier for a bean. ]]></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
        <xsd:attribute name="name" type="xsd:string">
            <xsd:annotation>
                <xsd:documentation><![CDATA[ The name of bean. ]]></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
        <xsd:attribute name="package" type="xsd:string">
            <xsd:annotation>
                <xsd:documentation><![CDATA[ The scan package. ]]></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
    </xsd:complexType>
    <xsd:element name="annotation" type="annotationType">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ The annotation config ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:element>
</xsd:schema>

至此,一个简单的自定义命名空间已经完成,我们将该项目打成 jar 包到本地仓库。

全部文件的结构图如下:

image.png

 

使用自定义命名空间


上面我们将自定义命名空间项目打成 jar 包到本地仓库,下面我们来简单的使用一下。

1. maven 中添加依赖

<dependency>
    <groupId>com.joonwhee</groupId>
    <artifactId>open-joonwhee-annotation</artifactId>
    <version>1.0.0</version>
</dependency>

2.在配置文件中使用自定义命名空间,例如:

image.png

3.写个 main 方法测试一下


package com.joonwhee.open.demo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * @author joonwhee
 * @date 2019/1/26
 */
public class Main {
    public static void main(String[] args) {
        ApplicationContext applicationContext = 
                new ClassPathXmlApplicationContext("classpath*:/config/spring/appcontext-*.xml");
        System.out.println(applicationContext.getBean("dog"));
    }
}

运行后输出:DogBean{name='Spike', basePackage='com.joonwhee.open.demo.service'}

相关文章
|
8月前
|
搜索推荐 JavaScript Java
基于springboot的儿童家长教育能力提升学习系统
本系统聚焦儿童家长教育能力提升,针对家庭教育中理念混乱、时间不足、个性化服务缺失等问题,构建科学、系统、个性化的在线学习平台。融合Spring Boot、Vue等先进技术,整合优质教育资源,提供高效便捷的学习路径,助力家长掌握科学育儿方法,促进儿童全面健康发展,推动家庭和谐与社会进步。
|
XML Java 测试技术
Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)
Spring 第三节 IOC——基于注解配置和管理Bean 万字详解!
1037 26
|
监控 Java 应用服务中间件
微服务——SpringBoot使用归纳——为什么学习Spring Boot
本文主要探讨为什么学习Spring Boot。从Spring官方定位来看,Spring Boot旨在快速启动和运行项目,简化配置与编码。其优点包括:1) 良好的基因,继承了Spring框架的优点;2) 简化编码,通过starter依赖减少手动配置;3) 简化配置,采用Java Config方式替代繁琐的XML配置;4) 简化部署,内嵌Tomcat支持一键式启动;5) 简化监控,提供运行期性能参数获取功能。此外,从未来发展趋势看,微服务架构逐渐成为主流,而Spring Boot作为官方推荐技术,与Spring Cloud配合使用,将成为未来发展的重要方向。
572 0
微服务——SpringBoot使用归纳——为什么学习Spring Boot
|
8月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
733 70
|
10月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
1394 0
|
12月前
|
XML 人工智能 Java
Spring IOC 到底是什么?
IOC(控制反转)是一种设计思想,主要用于解耦代码,简化依赖管理。其核心是将对象的创建和管理交给容器处理,而非由程序直接硬编码实现。通过IOC,开发者无需手动new对象,而是由框架负责实例化、装配和管理依赖对象。常见应用如Spring框架中的BeanFactory和ApplicationContext,它们实现了依赖注入和动态管理功能,提升了代码的灵活性与可维护性。
289 1
|
安全 Java 数据库
Spring Boot 框架深入学习示例教程详解
本教程深入讲解Spring Boot框架,先介绍其基础概念与优势,如自动配置、独立运行等。通过搭建项目、配置数据库等步骤展示技术方案,并结合RESTful API开发实例帮助学习。内容涵盖环境搭建、核心组件应用(Spring MVC、Spring Data JPA、Spring Security)及示例项目——在线书店系统,助你掌握Spring Boot开发全流程。代码资源可从[链接](https://pan.quark.cn/s/14fcf913bae6)获取。
2006 3
|
XML Java 数据格式
Spring IoC容器的设计与实现
Spring 是一个功能强大且模块化的 Java 开发框架,其核心架构围绕 IoC 容器、AOP、数据访问与集成、Web 层支持等展开。其中,`BeanFactory` 和 `ApplicationContext` 是 Spring 容器的核心组件,分别定位为基础容器和高级容器,前者提供轻量级的 Bean 管理,后者扩展了事件发布、国际化等功能。
374 18