目录
代码块1:DefaultNamespaceHandlerResolver.resolve
代码块4:registerBeanDefinitionParser
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源码学习:createBean详解(上)
Spring IoC源码学习:createBean详解(下)
Spring IoC源码学习:finishRefresh 详解
我们通过Spring IoC:parseDefaultElement详解解析了默认命名空间节点的解析,本文将解析自定义命名空间节点的解析。
首先让我们回到Spring IoC:obtainFreshBeanFactory详解文末的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 默认命名空间节点的处理,例如: <bean id="test" class="" /> parseDefaultElement(ele, delegate); } else { // 1.2 自定义命名空间节点的处理,例如:<context:component-scan/>、<aop:aspectj-autoproxy/> delegate.parseCustomElement(ele); } } } } else { // 2.自定义命名空间的处理 delegate.parseCustomElement(root); } }
1.2 自定义命名空间节点的处理,见下面 parseCustomElement 方法。
public BeanDefinition parseCustomElement(Element ele) { return parseCustomElement(ele, null); } public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { // 1.拿到节点ele的命名空间,例如常见的: // <context> 节点对应命名空间: http://www.springframework.org/schema/context // <aop> 节点对应命名空间: 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详解。
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 放到缓存。
点击该方法,我们可以看到有很多个实现类,分别对应了不同的命名空间 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 缓存中。
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 */ public class DogNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { // 注册一个自定义的解析器, 用于解析命名空间下的annotation节点 registerBeanDefinitionParser("annotation", new DogAnnotationDefinitionParser()); } }
新建 DogAnnotationDefinitionParser.java,内容如下:
package com.joonwhee.open.annotation.spring; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.w3c.dom.Element; /** * @author joonwhee * @date 2019/2/16 */ public class DogAnnotationDefinitionParser implements BeanDefinitionParser { @Override public BeanDefinition parse(Element element, ParserContext parserContext) { RootBeanDefinition beanDefinition = new RootBeanDefinition(); beanDefinition.setBeanClass(DogBean.class); MutablePropertyValues mutablePropertyValues = beanDefinition.getPropertyValues(); // 添加name属性 if (element.hasAttribute("name")) { mutablePropertyValues.addPropertyValue("name", element.getAttribute("name")); } // 添加package属性 if (element.hasAttribute("package")) { mutablePropertyValues.addPropertyValue("package", element.getAttribute("package")); } String id = element.getAttribute("id"); // 拿到注册表, 注册BeanDefinition parserContext.getRegistry().registerBeanDefinition(id, beanDefinition); return beanDefinition; } }
其中用到的DogBean 代码如下:
package com.joonwhee.open.annotation.spring; /** * @author joonwhee * @date 2019/2/16 */ public class DogBean { private String name; private String basePackage; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPackage() { return basePackage; } public void setPackage(String basePackage) { this.basePackage = basePackage; } @Override public String toString() { return "DogBean{" + "name='" + name + '\'' + ", basePackage='" + basePackage + '\'' + '}'; } }
4.对应第2步 spring.schemas 内容中的 value,在 resource下新建目录:com.joonwhee.open.dog.config,在目下新建文件 dog-1.0.xsd,内容如下:
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns="http://open.joonwhee.com/schema/dog" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://open.joonwhee.com/schema/dog" elementFormDefault="qualified"> <xsd:complexType name="annotationType"> <xsd:attribute name="id" type="xsd:ID"> <xsd:annotation> <xsd:documentation><![CDATA[ The unique identifier for a bean. ]]></xsd:documentation> </xsd:annotation> </xsd:attribute> <xsd:attribute name="name" type="xsd:string"> <xsd:annotation> <xsd:documentation><![CDATA[ The name of bean. ]]></xsd:documentation> </xsd:annotation> </xsd:attribute> <xsd:attribute name="package" type="xsd:string"> <xsd:annotation> <xsd:documentation><![CDATA[ The scan package. ]]></xsd:documentation> </xsd:annotation> </xsd:attribute> </xsd:complexType> <xsd:element name="annotation" type="annotationType"> <xsd:annotation> <xsd:documentation><![CDATA[ The annotation config ]]></xsd:documentation> </xsd:annotation> </xsd:element> </xsd:schema>
至此,一个简单的自定义命名空间已经完成,我们将该项目打成 jar 包到本地仓库。
全部文件的结构图如下:
上面我们将自定义命名空间项目打成 jar 包到本地仓库,下面我们来简单的使用一下。
1.在 maven 中添加依赖
<dependency> <groupId>com.joonwhee</groupId> <artifactId>open-joonwhee-annotation</artifactId> <version>1.0.0</version> </dependency>
2.在配置文件中使用自定义命名空间,例如:
3.写个 main 方法测试一下
package com.joonwhee.open.demo; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author joonwhee * @date 2019/1/26 */ public class Main { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath*:/config/spring/appcontext-*.xml"); System.out.println(applicationContext.getBean("dog")); } }
运行后输出:DogBean{name='Spike', basePackage='com.joonwhee.open.demo.service'}