前一篇 聊一聊 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服务】。