前一篇 聊一聊 Spring 中的扩展机制(一) 中聊到了 ApplicationListener
、 ApplicationContextAware
、 BeanFactoryAware
三种机制。本篇将介绍 NamespaceHandler
的扩展使用。
相信很多小伙伴对于这几个类都不陌生,基本基于 java
实现的 RPC
框架都会使用,比如 Dubbo , SOFARpc 等。本文先从几个小 demo
入手,了解下基本的概念和编程流程,然后分析下 SOFARpc
中是如何使用的。
NamespaceHandler
是 Spring
提供的 命名空间处理器。下面这张图中,除了乱入的本篇 demo
中涉及到的 BridgeNameSpaceHandler
之外,其他均为 Spring
自身提供的。
bean
和
context
依赖,所以这也仅仅是一部分。图中我们常用的应该算是
AopNamespaceHandler
。
我们使用基于 xml
的 spring
配置时,可能需要配置如 <aop:config />
这样的标签,在配置这个标签之前,通常我们需要引入这个 aop
所在的命名空间:
<?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" /> 复制代码
关于AOP 可以了解下 聊一聊 AOP :表现形式与基础概念 ,这里不过多解释,下面就按照官方文档的流程 来写一个自定义 xml
,最终效果如下:
<bridge:application id="bridgeTestApplication" name="bridgeTestApplication" version="1.0" organization="bridge.glmapper.com" owner="leishu@glmapper"/> 复制代码
关于 xsd
文件的语法规则不在本篇范围之内,有兴趣的同学可以自行 google
。 下面这个文件很简单,定义的 element
name 为 application
,对应于 bridge:application
中的 application
。 attribute
就是上面效果展示中对应的几个属性名。
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:tool="http://www.springframework.org/schema/tool" xmlns="http://bridge.glmapper.com/schema/bridge" targetNamespace="http://bridge.glmapper.com/schema/bridge"> <xsd:import namespace="http://www.springframework.org/schema/beans"/> <xsd:complexType name="applicationType"> <xsd:attribute name="id" type="xsd:ID"/> <xsd:attribute name="name" type="xsd:string" use="required"/> <xsd:attribute name="version" type="xsd:string"/> <xsd:attribute name="owner" type="xsd:string"/> <xsd:attribute name="organization" type="xsd:string"/> </xsd:complexType> <xsd:element name="application" type="applicationType"/> </xsd:schema> 复制代码
In addition to the schema, we need a NamespaceHandler that will parse all elements of this specific namespace Spring encounters while parsing configuration files.
用编写的这个 NamespaceHandler
来解析配置文件。
具体说来 NamespaceHandler
会根据 schema
和节点名找到某个 BeanDefinitionParser
,然后由 BeanDefinitionParser
完成具体的解析工作。
Spring
提供了默认实现类 NamespaceHandlerSupport
和 AbstractSingleBeanDefinitionParser
,最简单的方式就是去继承这两个类。
这里通过继承 NamespaceHandlerSupport
这个抽象类来完成。
public class BridgeNamespaceHandler extends NamespaceHandlerSupport { public void init() { registerBeanDefinitionParser("application", new ApplicationBeanDefinitionParser()); } } 复制代码
这里实际上只是注册了一个解析器,具体的 BeanDefinitionParser
才是将 XML
元素映射到特定 bean
的。
这里直接通过实现 BeanDefinitionParser
接口的方式定义我们的 BeanDefinitionParser
实现类。关于 AbstractSingleBeanDefinitionParser
的使用在 SPFARpc
中会涉及到。
public class ApplicationBeanDefinitionParser implements BeanDefinitionParser { public BeanDefinition parse(Element element, ParserContext parserContext) { //beanDefinition RootBeanDefinition beanDefinition = new RootBeanDefinition(); beanDefinition.setBeanClass(ApplicationConfig.class); beanDefinition.setLazyInit(false); //解析id String id = element.getAttribute("id"); beanDefinition.getPropertyValues().add("id", id); //解析name beanDefinition.getPropertyValues().add("name", element.getAttribute("name")); //解析version beanDefinition.getPropertyValues().add("version", element.getAttribute("version")); //owner beanDefinition.getPropertyValues().add("owner", element.getAttribute("owner")); //organization beanDefinition.getPropertyValues().add("organization", element.getAttribute("organization")); parserContext.getRegistry().registerBeanDefinition(id, beanDefinition); return beanDefinition; } } 复制代码
这里我们需要了解的是开始解析自定义标签的时候,是通过 BeanDefinitionParserDelegate->parseCustomElement
方法来处理的,如下图所示:
通过 ele
元素拿到当前 namespaceUri
,也就是在 xsd
中定义的命名空间,接着委托给 DefaultNamespaceResolver
得到具体的 handler
( BridgenamspaceHandler
) , 然后执行 parse
解析。
http/://bridge.glmapper.com/schema/bridge= com.glmapper.extention.namespacehandler.BridgeNamespaceHandler http/://bridge.glmapper.com/schema/bridge.xsd=META-INF/bridge.xsd 复制代码
配置这个其实是为了让 Spring
在解析 xml
的时候能够感知到我们的自定义元素,我们需要把 NamespaceHandler
和 xsd
文件放到位于META-INF目录下的 spring.handlers
和 spring.schmas
文件中。这样就可以在 spring
配置文件中使用我们自定义的标签了。如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:bridge="http://bridge.glmapper.com/schema/bridge" 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.xsd http://bridge.glmapper.com/schema/bridge http://bridge.glmapper.com/schema/bridge.xsd"> <bridge:application id="bridgeTestApplication" name="bridgeTestApplication" version="1.0" organization="bridge.glmapper.com" owner="leishu@glmapper"/> </beans> 复制代码
验证下从容器中获取我们的 bean
:
public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:bean.xml"); ApplicationConfig applicationConfig = (ApplicationConfig) applicationContext.getBean("bridgeTestApplication"); System.out.println("applicationConfig = "+applicationConfig); } 复制代码
输出示例:
applicationConfig = ApplicationConfig { id=bridgeTestApplication, name='bridgeTestApplication', version='1.0', owner='leishu@glmapper', organization='bridge.glmapper.com' } 复制代码
整体来看,如果我们要实现自己的 xml
标签,仅需完成以下几步即可:
SOFARpc
中的 rpc.xsd
文件是集成在 sofaboot.xsd
文件中的,详细可见: sofa-boot
xsd
文件这里不贴了,有点长
先看下 spring.handlers
和 spring.schmas
配置:
http/://sofastack.io/schema/sofaboot= com.alipay.sofa.infra.config.spring.namespace.handler.SofaBootNamespaceHandler http/://sofastack.io/schema/sofaboot.xsd= META-INF/com/alipay/sofa/infra/config/spring/namespace/schema/sofaboot.xsd http/://sofastack.io/schema/rpc.xsd= META-INF/com/alipay/sofa/infra/config/spring/namespace/schema/rpc.xsd 复制代码
从 spring.handlers
找到 NamespaceHandler
: SofaBootNamespaceHandler
。
源码如下,这里看出来,并不是像上面我们自己写的那种方式那样,会有一个 BeanDefinitionParser
。这里其实设计的很巧妙,通过 spi
的方式来载入具体的 BeanDefinitionParser
。
public class SofaBootNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { ServiceLoader<SofaBootTagNameSupport> serviceLoaderSofaBoot = ServiceLoader.load(SofaBootTagNameSupport.class); //SOFABoot for (SofaBootTagNameSupport tagNameSupport : serviceLoaderSofaBoot) { this.registerTagParser(tagNameSupport); } } private void registerTagParser(SofaBootTagNameSupport tagNameSupport) { if (!(tagNameSupport instanceof BeanDefinitionParser)) { // log return; } String tagName = tagNameSupport.supportTagName(); registerBeanDefinitionParser(tagName, (BeanDefinitionParser) tagNameSupport); } } 复制代码
这里可以看出有 ReferenceDefinitionParser
和 ServiceDefinitionParser
两个解析类,分别对应服务引用和服务暴露。
下面以 ReferenceDefinitionParser
为例,先看下它的类图:
解析工作都是在 AbstractContractDefinitionParser
类中完成, ReferenceDefinitionParser
自己只是做了一些特殊处理【jvm-first,jvm服务】。