紧接着上一篇关于spring默认标签加载,这一篇来看下自定义标签的加载
继续从 DefaultBeanDefinitionDocumentReader来看
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); 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)) { this.parseDefaultElement(ele, delegate); // 默认标签解析 } else { delegate.parseCustomElement(ele); // 自定义标签解析 } } } } else { delegate.parseCustomElement(root); // 自定义标签解析 } }
写在前边的东西,最近结合着《架构整洁之道》和《spring源码深度解析》这两本书一块儿看着,架构整洁之道里描述的一些面向对象的开发原则,接口隔离/单一职责/开闭幕式/依赖反转/里式替换
这些原则,在spring的源码里可谓是用的淋漓尽致,尤其单一职责/接口隔离,这两个翻看源码的时候尤其有体会,之前自己在项目开发中,其实根本没有在一这些事情,只是按照业务划分进行接口的拆分,并不在意是否是单一职责/是否接口隔离这些事情,其实单一职责能让我们更好的去拓展和维护我们的代码。包括接口隔离其实都是这样的目的。能够符合这些规则,我们的代码就能更大限度的符合高可维护性/低耦合性这些要求,也就能实现最大限度的优化开发效率这件事情。
好了,扯了些题外话,我们继续自定义标签的解析工作:
public BeanDefinition parseCustomElement(Element ele) { return this.parseCustomElement(ele, (BeanDefinition)null); }
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { String namespaceUri = this.getNamespaceURI(ele); NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } else { return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); } }
看到这里有点儿蒙圈,如果没有用过自定义标签的话,会有些蒙,那我们不妨在第一篇的例子上我们搞一个自定义标签来试试。
2019.07.06 14:29分 于图书馆继续(在家带了三天孩子,今天继续,上午继续看《架构整洁之道》不过有点儿瞌睡,中间看了一个小时的曾国藩家书,曾国藩说敬字/勤字,个个方面来说都要早起,而且早起更是曾氏一门,延续了好几代人的好习惯,并要求子女家人也要这样,是啊,想想,业精于勤荒于嬉,不说的都是这个意思吗?)早上起的太晚,习惯了现在连孩子都是这样了,这样下去下一代也还将延续我的毛病,如果现在不痛下决心就很难改变了。
好了,穿插下想说的话,现在继续。刚才翻了之前的两篇博客,写的还是不够好,自己看起来都有点儿晕,不过很快串联起来就好了,这两篇默认标签和自定义标签包括第一篇的xml文件到Doc的解析,这些相关的内容无非就是Bean的解析过程,认准了这个核心,带着这个核心去看就不难明白这其中的流程了。
前文提到,我们自己构建一个例子来看下自定义标签到底是如何使用的。这里就来展示下之前构建的例子。
创建一个自定义标签的步骤如下:
1:创建一个需要扩展的组件
2:定义一个XSD文件描述组件内容
3:创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件重的定义和组建定义
4:创建一个Handle文件,扩展自NamespaceHandlerSupport目的是将组件注册到Spring容器
5:编写spring.handlers和spring.schemas文件
OK,根据这个步骤我们来操作:
1:创建组件
public class User { private String userName; private String email; // ... 省略get set方法 }
2:定义XSD文件
创建了spring-test.xsd文件在META-INF下:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <xsd:schema xmlns="http://www.zaojiaoshu.com/schema/user" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.zaojiaoshu.com/schema/user" elementFormDefault="qualified" > <xsd:element name="user"> <xsd:complexType> <xsd:attribute name="id" type="xsd:string"/> <xsd:attribute name="userName" type="xsd:string"/> <xsd:attribute name="email" type="xsd:string"/> </xsd:complexType> </xsd:element> </xsd:schema>
目录
3:创建BeanDefinition接口实例,来解析XSD文件
package com.zaojiaoshu.learn.selfTag; import com.zaojiaoshu.learn.entity.User; 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 { protected Class<?> getBeanClass(Element element) { return User.class; } protected void doParse(Element element, BeanDefinitionBuilder builder) { String userName = element.getAttribute("userName"); String email = element.getAttribute("email"); if(StringUtils.hasText(userName)){ builder.addPropertyValue("userName", userName); } if(StringUtils.hasText(email)){ builder.addPropertyValue("email", email); } } }
4:创建Handle文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器
package com.zaojiaoshu.learn.selfTag; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; public class MyNameSpaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("user", new UserBeanDefinitionParser()); } }
5:创建spring.handlers和spring.schemas文件
spring.handlers
http\://www.zaojiaoshu.com/schema/user=com.zaojiaoshu.learn.selfTag.MyNameSpaceHandler
spring.schemas
http\://www.zaojiaoshu.com/schema/user.xsd=META-INF/spring-test.xsd
至此,一个自定义标签需要的工作,就都完成了。我们来做个测试在我们项目的ioc.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:myname="http://www.zaojiaoshu.com/schema/user" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.zaojiaoshu.com/schema/user http://www.zaojiaoshu.com/schema/user.xsd"> <myname:user id="testSelfBean" userName="aaa" email="bbb"/> </beans>
测试代码:
public class ServerMain { public static void main(String args[]){ BeanFactory context = new XmlBeanFactory(new ClassPathResource("ioc.xml")); User user = (User) context.getBean("testSelfBean"); System.out.println(user.getUserName() + "" + user.getEmail() + "米"); }
执行结果:
aaabbb米
好了,这样我们的一个自定义标签的例子就完成了,那么我们就带着问题来翻看自定义标签的解析源码吧。首先第一个问题,关于为什么META-INF/spring.handlers和META-INF/spring.schemas这两个文件,为什么会被加载到?
对,这是我本能的第一个好奇,why?你告诉我在这里写的,但是为什么?
上文的自定义解析的代码如下:
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { String namespaceUri = this.getNamespaceURI(ele); NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } else { return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); } }
我们来看这一句,看上去就是根据Namespace找到对应的Resolver来进行resolve。其实就是这一句起作用。
rederContext.getNamespaceHandlerResolver()方法,返回的实例对象是:DefaultNamespaceHandlerResolver,这个在构建BeanDefinitionDocuemntReader的时候可以看到。
public XmlReaderContext createReaderContext(Resource resource) { return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor, this, this.getNamespaceHandlerResolver()); } public NamespaceHandlerResolver getNamespaceHandlerResolver() { if (this.namespaceHandlerResolver == null) { this.namespaceHandlerResolver = this.createDefaultNamespaceHandlerResolver(); } return this.namespaceHandlerResolver; } protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() { return new DefaultNamespaceHandlerResolver(this.getResourceLoader().getClassLoader()); }
知道了是DefaultNamespaceHandlerResolver对象,那么我们来看它的resolve方法
public NamespaceHandler resolve(String namespaceUri) { Map<String, Object> handlerMappings = this.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"); } else { NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass); namespaceHandler.init(); handlerMappings.put(namespaceUri, namespaceHandler); return namespaceHandler; } } catch (ClassNotFoundException var7) { throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "] not found", var7); } catch (LinkageError var8) { throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]: problem with handler class file or dependent class", var8); } } }
头两句,一句获取handlerMappings映射关系,第二句根据映射关系拿到对应的处理类。
第一句:
private Map<String, Object> getHandlerMappings() { if (this.handlerMappings == null) { synchronized(this) { if (this.handlerMappings == null) { try { Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader); if (this.logger.isDebugEnabled()) { this.logger.debug("Loaded NamespaceHandler mappings: " + mappings); } Map<String, Object> handlerMappings = new ConcurrentHashMap(mappings.size()); CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings); this.handlerMappings = handlerMappings; } catch (IOException var5) { throw new IllegalStateException("Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", var5); } } } } return this.handlerMappings; }
看到,是个加载属性文件的方法,里边的this.handlerMappingLocation我们留意下。这个location其实在上边构造函数里其实已经指定了。我们来看下DefaultNamespasceHandlerResolver的构造函数。
public DefaultNamespaceHandlerResolver() { this((ClassLoader)null, "META-INF/spring.handlers"); } public DefaultNamespaceHandlerResolver(ClassLoader classLoader) { this(classLoader, "META-INF/spring.handlers"); } public DefaultNamespaceHandlerResolver(ClassLoader classLoader, String handlerMappingsLocation) { this.logger = LogFactory.getLog(this.getClass()); Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null"); this.classLoader = classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader(); this.handlerMappingsLocation = handlerMappingsLocation; }
两个构造函数都是默认的文件位置,所以这个spring.handlers文件我们一定是已经加载了的。
所以这里的mapping里一定有,我们spring.handlers文件里的定义
http\://www.zaojiaoshu.com/schema/user=com.zaojiaoshu.learn.selfTag.MyNameSpaceHandler
那么,上边的第二句代码user这个element的namespace就是 http://www.zaojiaoshu.com/schema/user ,那么获取的class就是:com.zaojiaoshu.learn.selfTag.MyNameSpaceHandler类。
接下来做的事情就是:加载这个类,实例化这个类,调用这个类的init()方法。我们在上边的代码上能表清晰的看到这些步骤。而init方法是我们在实例代码里MyNameSpaceHanlder类里的唯一一个方法,目的就是注册解析类。
同时,resolve方法返回的是一个NamespaceHandler对象。
我们继续上文parseCustomeElement的代码,调用handler的parse方法:
public BeanDefinition parse(Element element, ParserContext parserContext) { return this.findParserForElement(element, parserContext).parse(element, parserContext); } private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { String localName = parserContext.getDelegate().getLocalName(element); BeanDefinitionParser parser = (BeanDefinitionParser)this.parsers.get(localName); if (parser == null) { parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element); } return parser; }
第一句两件事情,1:根据element找到对应的parser,然后调用parser的parse方法
查看findParserForElement 的代码,可以看到,parsers里根据localName获取的。这里这个localName对于我们的标签来说肯定就是:user了。
我们还记得MyNamespaceHandler的init代码:
registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
调用的无非就是:NamespaceHandlerSupport 里的:
protected final void registerBeanDefinitionDecorator(String elementName, BeanDefinitionDecorator dec) { this.decorators.put(elementName, dec); }
那就可以看到了,我们自定义的Handler里做的就是注册user这个key到NamespaceHandlerSupport 的parsers列表里,然后在解析的时候,这里获取的就是这个parser了,相应的调用的就是我们自定义的那个parser的parse方法了,返回了BeanDefinition对象。
继续解析的parse方法:
public final BeanDefinition parse(Element element, ParserContext parserContext) { AbstractBeanDefinition definition = this.parseInternal(element, parserContext); if (definition != null && !parserContext.isNested()) { try { String id = this.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 (this.shouldParseNameAsAliases()) { String name = element.getAttribute("name"); if (StringUtils.hasLength(name)) { aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name)); } } BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases); this.registerBeanDefinition(holder, parserContext.getRegistry()); if (this.shouldFireEvents()) { BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder); this.postProcessComponentDefinition(componentDefinition); parserContext.registerComponent(componentDefinition); } } catch (BeanDefinitionStoreException var8) { parserContext.getReaderContext().error(var8.getMessage(), element); return null; } } return definition; }
这个parser当然就是我们之前创建的自定义的parser对象,我们继承了
AbstractSimpleBeanDefinitionParser
这个类。我们来看下类图:
所以第一句partnerInternal调用的是:AbstractSingleBeanDefinitionParser也即子类的实现
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); String parentName = this.getParentName(element); if (parentName != null) { builder.getRawBeanDefinition().setParentName(parentName); } Class<?> beanClass = this.getBeanClass(element); if (beanClass != null) { builder.getRawBeanDefinition().setBeanClass(beanClass); } else { String beanClassName = this.getBeanClassName(element); if (beanClassName != null) { builder.getRawBeanDefinition().setBeanClassName(beanClassName); } } builder.getRawBeanDefinition().setSource(parserContext.extractSource(element)); if (parserContext.isNested()) { builder.setScope(parserContext.getContainingBeanDefinition().getScope()); } if (parserContext.isDefaultLazyInit()) { builder.setLazyInit(true); } this.doParse(element, parserContext, builder); return builder.getBeanDefinition(); } protected String getParentName(Element element) { return null; } protected Class<?> getBeanClass(Element element) { return null; } protected String getBeanClassName(Element element) { return null; } protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { this.doParse(element, builder); } protected void doParse(Element element, BeanDefinitionBuilder builder) { }
这里一看就是明显的模版方法,惯用的套路,我们实现的就是最下边那个方法doParse(Element element, BeanDefinitionBuilder builder) ,层层往上,我们只是实现了一个自定义的parser。
还记得我们做了什么事情吗?来看下我们的UserBeanDefinition:
protected void doParse(Element element, BeanDefinitionBuilder builder) { String userName = element.getAttribute("userName"); String email = element.getAttribute("email"); if(StringUtils.hasText(userName)){ builder.addPropertyValue("userName", userName); } if(StringUtils.hasText(email)){ builder.addPropertyValue("email", email); } }
做的事情无非是把解析到的value值添加到builder的propertyValue属性里。然后返回了BeanDefinition,回到最初的调用链 AbstractBeanDefinitionParser的parse方法里。
接下去做的事情就比较熟悉了,尤其是那句
this.registerBeanDefinition(holder, parserContext.getRegistry());
无非就是跟之前的默认标签的最终操作一样,把我们的解析到的BeanDefinition封装成BeanDefinitionHolder然后,注册到我们的注册器中,最终添加到DefaultListableBeanFactory的那个concurrentHashMap里。
至此,我们的自定义标签的解析就结束了,相对默认标签的解析,这里的工作因为是使用自定义的Handler和parser,复杂程度,就主要在customer的自定义复杂程度上了,本身的解析复杂度,因为有了之前默认标签的解析对比,这里就轻松多了。
经过了这三篇的学习,我们把spring的xml经过
xml --> Doc --> BeanDefinition --> 注册到BeanFactory里,这几个步骤就完成了,我们接下来的工作就进入到最最核心的加载步骤了。期待。。。
2019-07-06 17:19于图书馆