Spring自定义命名空间的解析原理与实现

原理

由上篇文章refresh() -> obtainFreshBeanFactory()跟踪源码可知Spring在解析除默认命名空间import、alias、bean、beans以外的命名空间都会调用BeanDefinitionParserDelegate的**BeanDefinition parseCustomElement(Element ele)**方法进行解析

BeanDefinitionParserDelegate -> parseCustomElement()

public BeanDefinition parseCustomElement(Element ele) {return parseCustomElement(ele, null);
}@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {// 获取对应的命名空间String namespaceUri = getNamespaceURI(ele);if (namespaceUri == null) {return null;}// 根据命名空间找到对应的NamespaceHandlerNamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler == null) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);return null;}// 调用自定义的NamespaceHandler进行解析return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

DefaultNamespaceHandlerResolver -> resolve():获取handler的方法

resolve()中可识别找到自定义的NamespaceHandler对象

public NamespaceHandler resolve(String namespaceUri) {// 获取所有已经配置好的handler映射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 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的初始化方法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);}}
}

NamespaceHandlerSupport -> parse():找到对应的handler进行解析

public BeanDefinition parse(Element element, ParserContext parserContext) {// 获取元素的解析器BeanDefinitionParser parser = findParserForElement(element, parserContext);return (parser != null ? parser.parse(element, parserContext) : null);
}

通常调用的为AbstractBeanDefinitionParser -> parse()

public final BeanDefinition parse(Element element, ParserContext parserContext) {// 主要实现自定义解析方法AbstractBeanDefinition definition = parseInternal(element, parserContext);if (definition != null && !parserContext.isNested()) {try {String id = resolveId(element, definition, parserContext);if (!StringUtils.hasText(id)) {parserContext.getReaderContext().error("Id is required for element '" + parserContext.getDelegate().getLocalName(element)+ "' when used as a top-level tag", element);}String[] aliases = null;if (shouldParseNameAsAliases()) {String name = element.getAttribute(NAME_ATTRIBUTE);if (StringUtils.hasLength(name)) {aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));}}// 将AbstractBeanDefinition转换为BeanDefinitionHolder并注册BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);registerBeanDefinition(holder, parserContext.getRegistry());if (shouldFireEvents()) {// 通知监听器进行处理BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);postProcessComponentDefinition(componentDefinition);parserContext.registerComponent(componentDefinition);}}catch (BeanDefinitionStoreException ex) {String msg = ex.getMessage();parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);return null;}}return definition;
}

AbstractSingleBeanDefinitionParser -> parseInternal() 其中 doParse() 为自定义实现解析

protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();String parentName = getParentName(element);if (parentName != null) {builder.getRawBeanDefinition().setParentName(parentName);}// 获取自定义标签中的class,此时会调用自定义解析器Class<?> beanClass = getBeanClass(element);if (beanClass != null) {builder.getRawBeanDefinition().setBeanClass(beanClass);}else {// 若子类没有重写getBeanClass方法则尝试检查子类是否重写getBeanClassName方法String beanClassName = getBeanClassName(element);if (beanClassName != null) {builder.getRawBeanDefinition().setBeanClassName(beanClassName);}}builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));BeanDefinition containingBd = parserContext.getContainingBeanDefinition();if (containingBd != null) {// Inner bean definition must receive same scope as containing bean.// 若存在父类则使用父类的scope属性builder.setScope(containingBd.getScope());}if (parserContext.isDefaultLazyInit()) {// Default-lazy-init applies to custom bean definitions as well.// 配置延迟加载builder.setLazyInit(true);}// 调用子类重写的doParse方法进行解析doParse(element, parserContext, builder);return builder.getBeanDefinition();
}

AbstractSingleBeanDefinitionParser -> doParse() 调用子类重写的doParse方法进行解析

protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {doParse(element, builder);
}// 实际执行(什么也没做,预示子类实现自行扩展功能)
protected void doParse(Element element, BeanDefinitionBuilder builder) {}

实现

  1. 自定义xsd文件,模仿spring中的xsd文件进行自定义xsd文件编写

  2. 自定义实体类定义属性对应xsd中对应的自定义标签

  3. 自定义BeanDefinitionParser类继承org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser重写

    1. Class<?> getBeanClass(Element element):返回自定义实体的class对象
    2. void doParse(Element element, BeanDefinitionBuilder builder):实现重文档Element中获取参数存入BeanDefinitionBuilder
  4. 自定义NamespaceHandler处理类继承org.springframework.beans.factory.xml.NamespaceHandlerSupport重写

    • init():调用void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser)将具体的自定义标签名与自定义的解析转换对象以kv形式存入map集合
  5. resource目录下新建META-INF文件夹

  6. META-INF文件夹中新增

    1. spring.handlers文件:模仿spring中的handler文件

    2. spring.schemas文件:模仿spring中的schemas文件

      在自定义时可能spring.handlers/spring.schemas这种写法可能会抛出异常,将s改为S即可
      异常信息

      FAILURE: Build failed with an exception.* Where:
      Script '/Users/armin/IdeaProjects/spring-framework/gradle/docs.gradle' line: 228* What went wrong:
      A problem occurred evaluating script.
      > assert shortName != key|         |  ||         |  'http://www.armin.com/schema/user.xsd'|         false'http://www.armin.com/schema/user.xsd'* Try:
      Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.* Get more help at https://help.gradle.orgBUILD FAILED in 659ms
      

      或如以拉下源码在源码中找到spring-framework -> gradle -> docs.gradle中注释以下代码

      //       for (def key : schemas.keySet()) {
      //          def shortName = key.replaceAll(/http.*schema.(.*).spring-.*/, '$1')
      //          assert shortName != key
      //          File xsdFile = module.sourceSets.main.resources.find {
      //              (it.path.endsWith(schemas.get(key)) || it.path.endsWith(schemas.get(key).replaceAll('\\/','\\\\')))
      //          }
      //          assert xsdFile != null
      //          into (shortName) {
      //              from xsdFile.path
      //          }
      //      }
      
  7. 使用时在xml文件中像引入其他命名空间一样引入即可使用

1.自定义xsd文件

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"targetNamespace="http://www.armin.com/schema/user"xmlns:tns="http://www.armin.com/schema/user"elementFormDefault="qualified"><element name="user"><complexType><attribute name ="id" type = "string"/><attribute name ="username" type = "string"/><attribute name ="email" type = "string"/><attribute name ="age" type="string"/></complexType></element>
</schema>

2.自定义实体类定义接收属性

@Data
public class User {private String username;private String email;private String age;
}

3.自定义parse对象

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {@Overrideprotected Class<?> getBeanClass(Element element) {return User.class;}@Overrideprotected void doParse(Element element, BeanDefinitionBuilder builder) {String username = element.getAttribute("username");String email = element.getAttribute("email");String age = element.getAttribute("age");if (StringUtils.hasText(username)) {builder.addPropertyValue("username", username);}if (StringUtils.hasText(email)) {builder.addPropertyValue("email", email);}if (StringUtils.hasText(age)) {builder.addPropertyValue("age", age);}}
}

4.自定义NamespaceHandler对象

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;public class UserNamespaceHandler extends NamespaceHandlerSupport {@Overridepublic void init() {registerBeanDefinitionParser("user", new UserBeanDefinitionParser());}}

5.resource文件夹新建META-INF文件夹

6.新建spring.handlers与spring.schemas文件

spring.handlers

http\://www.armin.com/schema/user=com.armin.selftag.UserNamespaceHandler

spring.schemas

http\://www.armin.com/schema/user.xsd=META-INF/user.xsd

7.在xml文件中引入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:armin="http://www.armin.com/schema/user"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.armin.com/schema/user http://www.armin.com/schema/user.xsd"><armin:user id="armin" username="enzo" email="enzo@armin.com" age="18"/>
</beans>

测试

import com.armin.selftag.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class Test {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");User armin = (User) context.getBean("armin");System.out.println(armin);}}

测试结果

> Task :spring-debug:Test.main()
User{username='enzo', email='enzo@armin.com', age='18'}

Spring自定义命名空间的解析原理与实现相关推荐

  1. Spring自定义命名空间

    Spring自定义命名空间提供了一种很好的方式来简化用于描述Spring应用程序上下文的bean定义的xml文件. 这是一个相当古老的概念,最初是在Spring 2.0中引入的,但值得不时地进行审查. ...

  2. Spring自定义标签使用及原理

    最近大半年一直在看spring的源码,寻思着需要写点什么,也锻炼下自己文档编写的能力.本篇我们讲解spring自定义标签的使用及原理,分为以下小节进行讲解. 自定义标签的用途 自定义标签使用 自定义标 ...

  3. spring MVC使用自定义的参数解析器解析参数

    目录 写在前面 编写自定义的参数解析器解析请求参数 项目结构 定义注解 实体类 controller 定义参数解析器 注册参数解析器 启动项目 发起请求查看结果 写在前面 如果还有小伙伴不知道spri ...

  4. Spring源码深度解析(郝佳)-学习-第二章-容器的基本实现

    DefaultListableBeanFactory XmlBeanFactory继承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整 ...

  5. return error怎么定义_这一次搞懂Spring自定义标签以及注解解析原理

    自定义标签解析原理 在上一篇分析默认标签解析时看到过这个类DefaultBeanDefinitionDocumentReader的方法parseBeanDefinitions:拉勾IT课小编为大家分解 ...

  6. Spring源码解析:自定义标签的解析过程

    2019独角兽企业重金招聘Python工程师标准>>> spring version : 4.3.x Spring 中的标签分为默认标签和自定义标签两类,上一篇我们探究了默认标签的解 ...

  7. 【死磕 Spring】----- IOC 之解析 bean 标签:解析自定义标签

    前面四篇文章都是分析 Bean 默认标签的解析过程,包括基本属性.六个子元素(meta.lookup-method.replaced-method.constructor-arg.property.q ...

  8. Spring源码深度解析(郝佳)-学习-Spring Boot体系原理

      Spring Boot是由Pivotal团队提供的全新框架,其设计目的用来简化新Spring应用初始化搭建以及开发过程,该框架使用了我写的方式进行配置,从而开发人员不再需要定义样板化的配置,通过这 ...

  9. 开发 Spring 自定义视图和视图解析器

    Spring 视图和视图解析器简介 什么是 Spring 视图和视图解析器 Spring MVC(Model View Controller)是 Spring 中一个重要的组成部分,而 Spring ...

最新文章

  1. h5 返回上一页并且刷新页面
  2. sphinx error connection to 127.0.0.1:9312 failed (errno=0, msg=)
  3. keepalived 多实例
  4. 计算机的四个硬盘有什么区别是什么意思,笔记本的内存和硬盘有什么区别 原来笔记本硬盘有这几种...
  5. 英语总结系列(二十二):Baby偶遇GCT
  6. Java 连接 SQL Server 数据库
  7. 测试 JavaScript 函数的性能
  8. oracle打开dmp文件乱码,oracle中导入dmp字符乱码分析和解决方案
  9. 高端驱动和低端驱动--ir2110
  10. <el-link>去掉下划线
  11. 有开始边DOTA边博客了
  12. 原来 Elasticsearch 还可以这么理解
  13. python进阶练习题:IRR计算 - 盈利能力的评价【难度:2级】--景越Python编程实例训练营,不同难度Python习题,适合自学Python的新手进阶
  14. linux解压时,z x v f分别代表什么意思
  15. 抖音趣味测试、心理测试类短视频素材哪里找?文案怎么写?技巧大汇总
  16. IP地址、子网掩码、网关
  17. 互联网晚报|12/27星期二| ​​国家卫健委:取消入境后全员核酸检测和集中隔离;新冠肺炎更名为新冠感染;知网回应被罚8760万...
  18. 有趣的灵魂不多,但有趣的设计素材这里很多
  19. Android 声音分贝控制锁屏demo实现
  20. 数学之美:激发思考的奥秘

热门文章

  1. Mac上执行Linux可执行文件报错:cannot execute binary file
  2. 12个MySQL慢查询的原因分析
  3. 两路音频合成一路电路_一种无源多路音频合路处理模块的制作方法
  4. 【《MySQL 8 参考手册》中文翻译】前言
  5. 关于数据标注工具LabelImg软件的下载与使用
  6. flume伪分布模式实践
  7. cocos2dx3.x的EditBox和ControlSlider使用
  8. 生物识别技术到底靠不靠谱?
  9. 高手勿入!直方图均衡化、规定化(匹配)
  10. arcgispro 影像分类