Spring源码解密之自定义标签与解析
前言 在 上一节 Spring解密 - 默认标签的解析 中,重点分析了 Spring 对默认标签是如何解析的,那么本章继续讲解标签解析,着重讲述如何对自定义标签进行解析。话不多说了,来一起看看详细的介绍吧。 自定义标签 在讲解 自定义标签解析 之前,先看下如何自定义标签 定义 XSD 文件 定义一个 XSD 文件描述组件内容 <?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns="http://www.battcn.com/schema/battcn" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans" targetNamespace="http://www.battcn.com/schema/battcn" elementFormDefault="qualified" attributeFormDefault="unqualified"> <xsd:import namespace="http://www.springframework.org/schema/beans" /> <xsd:element name="application"> <xsd:complexType> <xsd:complexContent> <xsd:extension base="beans:identifiedType"> <xsd:attribute name="name" type="xsd:string" use="required"/> </xsd:extension> </xsd:complexContent> </xsd:complexType> </xsd:element> </xsd:schema>
定义解析规则 1.创建一个类实现 BeanDefinitionParser 接口(也可继承 Spring 提供的类),用来解析 XSD 文件中的定义和组件定义 public class ApplicationBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override protected Class getBeanClass(Element element) { // 接收对象的类型 如:String name = (String) context.getBean("battcn"); return String.class; } @Override protected void doParse(Element element,BeanDefinitionBuilder bean) { // 在 xsd 中定义的 name 属性 String name = element.getAttribute("name"); bean.addConstructorArgValue(name); } } 这里创建了一个 ApplicationBeanDefinitionParser 继承 AbstractSingleBeanDefinitionParser(是:BeanDefinitionParser 的子类), 重点就是重写的 doParse,在这个里面解析 XML 标签的,然后将解析出的 value(Levin) 通过构造器方式注入进去 2.创建一个类继承 NamespaceHandlerSupport 抽象类 public class BattcnNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("application",new ApplicationBeanDefinitionParser()); } } BattcnNamespaceHandler 的作用特别简单,就是告诉 Spring 容器,标签 3.编写 spring.handlers 和 spring.schemas 文件 文件存放的目录位于 resources/META-INF/文件名 spring.handlers http://www.battcn.com/schema/battcn=com.battcn.handler.BattcnNamespaceHandler spring.schemas http://www.battcn.com/schema/battcn.xsd=battcn.xsd 4.使用自定义标签 申明 bean.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:battcn="http://www.battcn.com/schema/battcn" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.battcn.com/schema/battcn http://www.battcn.com/schema/battcn.xsd"> <battcn:application id="battcn" name="Levin"/> </beans> 创建一个测试类,如果看到控制台输出了 Levin 字眼,说明自定义标签一切正常 public class Application { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); String name = (String) context.getBean("battcn"); System.out.println(name); } } 5.如图所示 源码分析 自定义标签解析入口 public class BeanDefinitionParserDelegate { @Nullable public BeanDefinition parseCustomElement(Element ele,@Nullable BeanDefinition containingBd) { // 获取命名空间地址 http://www.battcn.com/schema/battcn String namespaceUri = getNamespaceURI(ele); if (namespaceUri == null) { return null; } // NamespaceHandler 就是 自定义的 BattcnNamespaceHandler 中注册的 application 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)); } } 与默认标签解析规则一样的是,都是通过 public class XmlBeanDefinitionReader { public NamespaceHandlerResolver getNamespaceHandlerResolver() { if (this.namespaceHandlerResolver == null) { this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver(); } return this.namespaceHandlerResolver; } } resolve 1.加载指定的 NamespaceHandler 映射,并且提取的 NamespaceHandler 缓存起来,然后返回 public class DefaultNamespaceHandlerResolver { @Override @Nullable public NamespaceHandler resolve(String namespaceUri) { Map<String,Object> handlerMappings = getHandlerMappings(); // 从 handlerMappings 提取 handlerOrClassName 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); // Handler 初始化 namespaceHandler.init(); handlerMappings.put(namespaceUri,namespaceHandler); 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); } } } } 标签解析 加载完 NamespaceHandler 之后,BattcnNamespaceHandler 就已经被初始化为 了,而 BattcnNamespaceHandler 也调用了 public class NamespaceHandlerSupport { @Override @Nullable public BeanDefinition parse(Element element,ParserContext parserContext) { BeanDefinitionParser parser = findParserForElement(element,parserContext); return (parser != null ? parser.parse(element,parserContext) : null); } @Nullable private BeanDefinitionParser findParserForElement(Element element,ParserContext parserContext) { // 解析出 <battcn:application /> 中的 application 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; } } 简单来说就是从 parsers 中寻找到 ApplicationBeanDefinitionParser 实例,并调用其自身的 doParse 方法进行进一步解析。最后就跟解析默认标签的套路一样了… 总结 熬过几个无人知晓的秋冬春夏,撑过去一切都会顺着你想要的方向走… 好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对编程小技巧的支持。 说点什么 全文代码:https://gitee.com/battcn/battcn-spring-source/tree/master/Chapter2 您可能感兴趣的文章:
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |