Spring IoC源码学习:parseCustomElement 详解

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 IoC:parseDefaultElement详解 解析了默认命名空间节点的解析,本文将解析自定义命名空间节点的解析。

正文

==

首先让我们回到 Spring IoC:obtainFreshBeanFactory详解 文末的 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 默认命名空间节点的处理,例如:

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的命名空间,例如常见的:

// 节点对应命名空间: http://www.springframework.org/schema/context

// 节点对应命名空间: 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 IoC:obtainFreshBeanFactory详解 中的代码块10,我们构建 XmlReaderContext 时会创建一个默认的DefaultNamespaceHandlerResolver,这边拿到的就是当时创建的 DefaultNamespaceHandlerResolver 对象,例如下图。

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

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

代码块1:DefaultNamespaceHandlerResolver.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详解

代码块2:getHandlerMappings


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

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

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

代码块3:namespaceHandler#init


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

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

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

代码块4:registerBeanDefinitionParser


private final Map<String, BeanDefinitionParser> parsers =

new HashMap<String, BeanDefinitionParser>();

// … …

protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {

this.parsers.put(elementName, parser);

}

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

代码块5:parse


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缓存如下:

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 的 Url:http://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

*/

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后

Java架构学习技术内容包含有:Spring,Dubbo,MyBatis, RPC, 源码分析,高并发、高性能、分布式,性能优化,微服务 高级架构开发等等。

还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书+2021年最新大厂面试题。
在这里插入图片描述

转存中…(img-YfzQBx70-1711990778089)]
[外链图片转存中…(img-KiRt7POo-1711990778089)]
[外链图片转存中…(img-8k6eW3je-1711990778090)]
[外链图片转存中…(img-3swr8q7P-1711990778090)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-pjNdtsHc-1711990778090)]

最后

Java架构学习技术内容包含有:Spring,Dubbo,MyBatis, RPC, 源码分析,高并发、高性能、分布式,性能优化,微服务 高级架构开发等等。

还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书+2021年最新大厂面试题。
[外链图片转存中…(img-aihQcTT7-1711990778090)]

  • 26
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值