前一篇 聊一聊 Spring 中的扩展机制(一) 中聊到了ApplicationListener
、ApplicationContextAware
、BeanFactoryAware
三种机制。本篇将介绍 NamespaceHandler
的扩展使用。php
相信不少小伙伴对于这几个类都不陌生,基本基于java
实现的RPC
框架都会使用,好比 Dubbo , SOFARpc 等。本文先从几个小demo
入手,了解下基本的概念和编程流程,而后分析下 SOFARpc
中是如何使用的。html
NamespaceHandler
是 Spring
提供的 命名空间处理器。下面这张图中,除了乱入的本篇 demo
中涉及到的 BridgeNameSpaceHandler
以外,其余均为 Spring
自身提供的。 java
bean
和
context
依赖,因此这也仅仅是一部分。图中咱们经常使用的应该算是
AopNamespaceHandler
。
咱们使用基于xml
的spring
配置时,可能须要配置如<aop:config />
这样的标签,在配置这个标签以前,一般咱们须要引入这个aop
所在的命名空间:git
<?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
,最终效果以下:github
<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
就是上面效果展现中对应的几个属性名。spring
<?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.apache
用编写的这个 NamespaceHandler
来解析配置文件。编程
具体说来NamespaceHandler
会根据schema
和节点名找到某个BeanDefinitionParser
,而后由BeanDefinitionParser
完成具体的解析工做。bash
Spring
提供了默认实现类NamespaceHandlerSupport
和AbstractSingleBeanDefinitionParser
,最简单的方式就是去继承这两个类。app
这里经过继承 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服务】。
本篇经过 NamespaceHandler
了解了如何去编写咱们自定义的xml标签,从NamespaceHandler
的角度能够很好的理解一些 RPC
框架中最基础的基于xml
方式的服务引用和暴露的实现思路。另外经过分析 SOFARpc
,也了解了在实际的工程组件中对于NamespaceHandler
的扩展使用。