聊一聊 Spring 中的扩展机制(二) - NamespaceHandler
相信很多小伙伴对于这几个类都不陌生,基本基于java实现的RPC框架都会使用,比如 Dubbo , SOFARpc 等。本文先从几个小demo入手,了解下基本的概念和编程流程,然后分析下 SOFARpc 中是如何使用的。
NamespaceHandler
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" />
<bridge:application id="bridgeTestApplication"
name="bridgeTestApplication"
version="1.0"
organization="bridge.glmapper.com"
owner="leishu@glmapper"/>
1、定义 xsd 文件
关于 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>
2、编写 NamespaceHandler
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的。
3、编写 BeanDefinitionParser
这里直接通过实现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 解析。
4、配置 spring.handlers 和 spring.schmas
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 标签,仅需完成以下几步即可:
- 1、定义 xsd 文件
- 2、编写 NamespaceHandler
- 3、编写 BeanDefinitionParser
- 4、配置 spring.handlers 和 spring.schmas
SOFARpc 中使用分析
SOFARpc 中的 rpc.xsd 文件是集成在 sofaboot.xsd 文件中的,详细可见:sofa-boot
xsd文件这里不贴了,有点长
spring.handlers 和 spring.schmas
先看下 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。
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的扩展使用。
转载自:https://juejin.cn/post/6844903665262657544