Spring
是一个开源的设计层面框架,解决了业务逻辑层和其他各层的松耦合问题,将面向接口的编程思想贯穿整个系统应用,同时它也是Java工作中
必备技能之一…
前言
在 上一节 Spring解密 - 默认标签的解析 中,重点分析了 Spring
对默认标签
是如何解析的,那么本章继续讲解标签解析,着重讲述如何对自定义标签
进行解析。
自定义标签
在讲解 自定义标签解析
之前,先看下如何自定义标签
定义 XSD 文件
定义一个 XSD 文件描述组件内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?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>
|
- 声明命名空间: 值得注意的是
xmlns
与 targetNamespace
可以是不存在,只要映射到指定 XSD
就行了。
- 定义复合元素: 这里的
application
就是元素的名称,使用时 <battcn:application id="battcn"/>
- 定义元素属性: 元素属性就是
attribute
标签,我们声明了一个必填的 name
的属性,使用时 <battcn:application id="battcn" name="Levin"/>
定义解析规则
1.创建一个类实现 BeanDefinitionParser
接口(也可继承 Spring
提供的类),用来解析 XSD 文件中的定义和组件定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class ApplicationBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
@Override protected Class getBeanClass(Element element) { return String.class; }
@Override protected void doParse(Element element, BeanDefinitionBuilder bean) { String name = element.getAttribute("name"); bean.addConstructorArgValue(name); } }
|
这里创建了一个 ApplicationBeanDefinitionParser
继承 AbstractSingleBeanDefinitionParser(是:BeanDefinitionParser 的子类)
, 重点就是重写的 doParse
,在这个里面解析 XML 标签的,然后将解析出的 value(Levin)
通过构造器方式注入进去
2.创建一个类继承 NamespaceHandlerSupport
抽象类
1 2 3 4 5 6 7 8
| public class BattcnNamespaceHandler extends NamespaceHandlerSupport {
@Override public void init() { registerBeanDefinitionParser("application", new ApplicationBeanDefinitionParser()); }
}
|
BattcnNamespaceHandler
的作用特别简单,就是告诉 Spring
容器,标签 <battcn:application />
应该由那个解析器解析(这里是我们自定义的:ApplicationBeanDefinitionParser
),负责将组件注册到 Spring
容器
3.编写 spring.handlers
和 spring.schemas
文件
文件存放的目录位于 resources/META-INF/文件名
spring.handlers
spring.schemas
4.使用自定义标签
申明 bean.xml
文件,定义如下
1 2 3 4 5 6 7 8 9 10 11 12
| <?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
字眼,说明自定义标签一切正常
1 2 3 4 5 6 7 8 9 10
| 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.如图所示

源码分析
自定义标签解析入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class BeanDefinitionParserDelegate {
@Nullable public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) { 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)); }
}
|
与默认标签解析规则一样的是,都是通过 getNamespaceURI(Node node)
来获取命名空间,那么 this.readerContext.getNamespaceHandlerResolver()
是从哪里获取的呢?我们跟踪下代码,可以发现在项目启动的时候,会在 XmlBeanDefinitionReader
将所有的 META-INF/spring.handles
文件内容解析,存储在 handlerMappers(一个ConcurrentHashMap)
中,在调用 resolve(namespaceUri)
校验的时候在将缓存的内容提取出来做对比
1 2 3 4 5 6 7 8 9 10
| public class XmlBeanDefinitionReader {
public NamespaceHandlerResolver getNamespaceHandlerResolver() { if (this.namespaceHandlerResolver == null) { this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver(); } return this.namespaceHandlerResolver; }
}
|
resolve
1.加载指定的 NamespaceHandler
映射,并且提取的 NamespaceHandler
缓存起来,然后返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public class DefaultNamespaceHandlerResolver {
@Override @Nullable 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 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("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
也调用了 init()
方法完成了初始化的工作。因此就接着执行这句代码: handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
具体标签解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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) { 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
- 个人QQ:1837307557
- battcn开源群(适合新手):391619659
微信公众号:battcn
(欢迎调戏)