1. 基于 Spring 容器的 Dubbo
Dubbo 采用 Spring 的方式进行组件的管理,支持 XML 以及注解式的配置。在使用时,我们仅仅需要通过 beanName 向 IoC 容器索要组件,IoC 容器就能够返回封装了 RPC 的代理类实例。
并且,我们可以发现,Dubbo 使用的就是原生的 Spring IoC 容器类(没有进行继承重写),下面分别是基于 XML 配置的 Dubbo 客户端以及基于注解的 Dubbo 客户端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//基于 XML 的 Dubbo 客户端
//1. 指定 XML 配置文件来创建 ClassPathXmlApplicationContext 实例
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-demo-consumer.xml");
//2. 启动容器
context.start();
//3. 通过 beanName 来得到具体的代理类实例
DemoService demoService = (DemoService) context.getBean("demoService");
//基于注解的 Dubbo 客户端
//1. 指定配置类 class 来创建 AnnotationConfigApplicationContext 实例
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class);
//2. 启动容器
context.start();
//3. 通过 beanName 来得到具体的代理类实例
final AnnotationAction annotationAction = (AnnotationAction) context.getBean("annotationAction");
|
这里的 Spring 相关的完整类名为:
- org.springframework.context.annotation.AnnotationConfigApplicationContext
- org.springframework.context.support.ClassPathXmlApplicationContext
这一切仿佛如魔法一般,我们在运行时完全不需要告诉 Spring 如何来解析 XML(或者注解)配置,也无需告诉 Spring 容器在通过 beanName 时需要返回的实例为 RPC 调用代理类实例。我们就是使用原生的 Spring 容器,那么其中的原理是什么呢?
2. Dubbo 如何告知 Spring IoC 容器来管理 Bean
类似于 SPI 机制,Spring 提供 XML schema 扩展机制来实现让 Spring 的 IoC 容器管理 Dubbo 中的 Bean 逻辑。这可以参考 Spring 官方文档。
Dubbo 框架的做法是:
-
在项目根目录下的 /META-INF 目录下存放一个配置文件:spring.handlers,配置的具体内容如下:
1
2
|
http\://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
|
上述配置显然引用了一个类:DubboNamespaceHandler
-
实现 NamespaceHandler 接口的具体实现类 NamespaceHandler;
-
实现多个 BeanDefinitionParser 接口的实现类,并通过 NamespaceHandler#init 方法进行注册;
-
在项目根目录下的 /META-INF 目录下存放一个配置文件:dubbo.xsd;
2.1 什么是 xsd 文件
引用于 wikipedia,XSD (XML Schema Definition)是W3C于2001年5月发布的推荐标准,指出如何形式描述XML文档的元素。XSD是许多XML Schema 语言中的一支。XSD是首先分离于XML本身的schema语言,故获取W3C的推荐地位。
换言之,xsd 文件主要用于规定 XML 文件的格式,在现代编辑器中,例如 IntelliJ,会根据 XSD 文件在配置 Bean 时进行格式检查以及提示。
以 Dubbo 的 dubbo.xsd 文件例子如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<?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://dubbo.apache.org/schema/dubbo"
targetNamespace="http://dubbo.apache.org/schema/dubbo">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans.xsd"/>
<xsd:import namespace="http://www.springframework.org/schema/tool"/>
<xsd:annotation>
<xsd:documentation>
<![CDATA[ Namespace support for the dubbo services provided by dubbo framework. ]]></xsd:documentation>
</xsd:annotation>
<!--限于篇幅,省略其他标签-->
</xsd:schema>
|
注意:xsd:schema
标签的 targetNamespace 属性很重要,2.3 小节会提到,其属性值必须与 META-INF/spring.handlers 配置文件中的属性名(key)保持一致。
参考 Spring 官方文档,我们可以知道 META-INF/spring.handlers
配置文件主要提供了一对映射关系:将 XML Schema URLs 映射为 namespace handler class。我们还是以 Dubbo 为例,如下所示:
1
2
|
http\://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
|
其中,由于 :
符号在 .properties 文件中属于一个有效的分隔符,因此我们需要使用一个额外的右折号 \
来进行转义。
上述每一对等号就是一副 key-value 对,其中 key 为 URL,必须与你自定义的 namespcace extension 相一致,具体来说,这个值必须与 xsd 文件中的 targetNamespace 属性保持一致。我们也将上述 url 称作 namespace url。
2.3 NamespaceHandler
NamespaceHandler 是一个接口,用于引入命令空间。通过我们会使用 NamespaceHandler 的抽象实现类 NamespaceHandlerSupport,后者提供了一些默认的实现逻辑,因此,事实上,DubboNamespaceHandler 的继承以及实现关系如下图所示:
1
2
3
|
public class DubboNamespaceHandler extends NamespaceHandlerSupport implements ConfigurableSourceBeanMetadataElement {
//省略其他代码
}
|
DubboNamespaceHandler#init 方法负责注册相关命名空间解析器,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@Override
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true));
registerBeanDefinitionParser("ssl", new DubboBeanDefinitionParser(SslConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}
|
这里完成的功能是为每一个命名空间指定一个解析器实例(因为都是重新 new 出来的,因此每个命令空间的解析器各部相同),然后为每一个命令空间指定对应的配置实例,例如:application 命令空间下的配置应当解析为 ApplicationConfig 类型实例。
为什么需要在不同的命名空间下使用独立的 BeanDefinitionParser 实例?
这是因为一个 DubboBeanDefinitionParser 只能满足将一类命令空间下的配置转换为一类 AbstractConfig 实例(这个类为 ApplicationConfig、ModuleConfig 等类的共同父类),因此不同命名空间无法共用一个 BeanDefinitionParser 实例。
这样说可能不太容易懂,下面举一个例子。
在 XML 配置如下内容:
1
2
|
## application
<dubbo:application id="applicationBean" name="dubbo-demo-application" port="1099" />
|
实际上等价于如下配置:
1
2
3
4
5
6
|
@Bean("applicationBean")
public ApplicationConfig applicationBean() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("dubbo-demo-application");
return applicationConfig;
}
|
可见,每一个 application 命令空间下的配置在被配置解析器解析后,会转换为 ApplicationConfig 类实例。我们在 DubboNamespaceHandler#init 方法中需要规定在具体的一个命名空间下,使用哪一个配置解析器以及将一行行配置转换为哪一种配置类实例。
2.4 BeanDefinitionParser 接口
BeanDefinitionParser 接口负责 XML 配置文件的解析,例如 Dubbo 框架提供了一个具体实现类 DubboBeanDefinitionParser 来完成 XML 配置文件的解析。
DubboBeanDefinitionParser 中的代码比较多,而且我目前并没有非常熟悉 Dubbo 的 XML 配置,因此并不能完全看懂。这里就说一个大概。
DubboBeanDefinitionParser 类通过暴露 DubboBeanDefinitionParser#parse 方法(这是一个具有多个重载版本的方法)完成配置类的解析:
- 首先构造一个 BeanDefinition 实例;
- 如果入口参数有指定 beanClass,那么使用入口参数的,如果没有,那么使用实例的 beanClass 字段,DubboBeanDefinitionParser 类构造器会接收一个
Class<?> beanClass
参数,例如上面的:ApplicationConfig.class、ModuleConfig.class 等类型;
- 得到 element(这里可以认为这是配置文件中的一项配置)元素的各个属性;
- 然后进行复杂的 BeanDefinition 实例初始化配置;
3. 源码解析
3.1 Dubbo 的配置文件
我们可以直接参考 Dubbo 在 GitHub 上的项目的 dubbo-config 模块:
https://github.com/apache/dubbo/tree/master/dubbo-config/dubbo-config-spring/src/main/resources/META-INF
可以看到我们在前面两节提到的配置文件:

3.2 Spring 的源码入口
https://juejin.im/post/6844903866593460231
Spring 源码的入口是 BeanDefinitionParserDelegate#parseCustomElement 方法来解析我们自定义的 XML 元素,其源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//BeanDefinitionParserDelegate#parseCustomElement 方法
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
//获取元素指定的 namespaceUri,例如:http://dubbo.apache.org/schema/dubbo/application
String namespaceUri = getNnamespaceUriamespaceURI(ele);
//根据 namespaceUri 获得指定的 NamespaceHandler 实例(这对应于 DubboNamespaceHandler#init 方法的注册逻辑)
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
//然后调用 NamespaceHandler#parse 方法来完成元素的解析
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
|
而 NamespaceHandler#parse 方法最终会依赖 DubboBeanDefinitionParser#parse 方法完成元素的解析。
我们再来看看 DefaultNamespaceHandlerResolver#resolve 方法是如何将 namespaceUri 映射为 NamespaceHandler 实例的,下面是其源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
//DefaultNamespaceHandlerResolver#resolve
public NamespaceHandler resolve(String namespaceUri) {
//这里将 namespaceUri 作为 key,得到 NamespaceHandler
Map<String, Object> handlerMappings = getHandlerMappings();
Object handlerOrClassName = handlerMappings.get(namespaceUri);
//1. null 检查
if (handlerOrClassName == null) {
return null;
}
//2. 如果 handlerOrClassName 类型就是 NamespaceHandler,那么直接返回
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
//3. 如果 handlerOrClassName 为 NamespaceHandler 子类的完整类名
else {
String className = (String) handlerOrClassName;
try {
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
//调用 NamespaceHandler#init 方法,对于 DubboNamespaceHandler 来说就是注册一大堆 BeanDefinitionParser 实例
namespaceHandler.init();
//暂存起来
handlerMappings.put(namespaceUri, namespaceHandler);
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);
}
}
}
|
最后,我们再来看看 spring.handlers 是如何被 Spring 加载的,首先我们在 DefaultNamespaceHandlerResolver 类中定义了如下字段:
1
|
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
|
这个字段指明了默认的 Handler 映射定位文件的位置。
其次,此字段会作为 DefaultNamespaceHandlerResolver 的 handlerMappingsLocation 字段的默认值进行初始化,如下所示:
1
2
3
4
|
//DefaultNamespaceHandlerResolver 的无参构造器会默认使用 DEFAULT_HANDLER_MAPPINGS_LOCATION
public DefaultNamespaceHandlerResolver() {
this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}
|
最后,DefaultNamespaceHandlerResolver#getHandlerMappings 方法会返回所有框架中的 spring.handlers 配置文件指定的映射关系,源代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
private Map<String, Object> getHandlerMappings() {
if (this.handlerMappings == null) {
synchronized (this) {
if (this.handlerMappings == null) {
try {
//这个步骤就是进行加载映射关系,这里的入口参数 handlerMappingsLocation 的值
//就是 DEFAULT_HANDLER_MAPPINGS_LOCATION 的值
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());
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
}
catch (IOException ex) {
throw new IllegalStateException(
"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
}
}
}
}
return this.handlerMappings;
}
|
例如,我们单单使用 Dubbo,在上述方法上进行打点,最终返回的 Map 有如下图所示的 key-value 键值对:

此时 key 以及 value 的类型都是字符串类型。
到这里,我们已经知道了为什么 Spring 容器能够支持加载 Dubbo 自定义的 XML 标签为 BeanDefinition,简单来说就是一句话:Spring 提供 XML 扩展机制,只要第三方架构在指定位置放置规定的配置文件、提供规定的配置文件解析器,那么 Spring 容器在启动时就会知道如何解析第三方框架配置文件为 BeanDefinition 实例。
但是,RPC 代理又在什么时候完成的呢?
这里以我自己的 Dubbo samples 项目为例(克隆来自于 apache/dubbo-samples)。我们进入 dubbo-samples-basic 模块。
- 先启动 BasicProvider#main 方法;
- 然后在 BasicConsumer#main 方法的
DemoService demoService = (DemoService) context.getBean("demoService");
语句下的 for 循环上打上断点(这样的目的是为了使 demoService 实例在 IoC 容器中得到初始化);
我们可以发现,demoService 实例的实际类型为:class org.apache.dubbo.common.bytecode.proxy0,整个结果如下图所示:

我们可以发现,点击类型上的 Navigate 并不能如平常一般跳转到类定,这是为什么?
这是因为:Dubbo 依赖于 javassist 实现动态代理,其动态代理是通过直接操作字节码来生成代理对象,因此其在动态生成了 org.apache.dubbo.common.bytecode.proxy.proxy0 类的字节码。不过,既然 demoService 实例能够通过强制类型转换,因此这个实例自然是接口 DemoService 的具体实现类。
但是,代理实例究竟是在什么时候生成的呢?
通过之前的介绍,我们已经知道了在通过 Spring XML schema 扩展机制,我们可以使用自定义的:XML 标签、BeanDefinitionParser 实例来完成将 XML 配置转换为 BeanDefinition 的原理。RPC 代理的生成则发生于 BeanDefition 生成完毕后。
Spring 容器获取到 BeanDefinition 后,BeanDefinition 会被注册到容器中,然后 Spring 容器会通过 BeanDefinition生成 Bean。
我们可以通过在 ReferenceConfig#createProxy 方法上打上断点,然后以 debug 运行 BasicProvider#main 方法一切都会变得明了,如下图所示:

其中 <dubbo:reference/>
标签用于创建一个远程服务代理,一个引用可以指向多个注册中心,例如:
1
2
|
<!--指定要消费的服务-->
<dubbo:reference id="demoService" check="true" interface="org.apache.dubbo.samples.basic.api.DemoService"/>
|
可以见得,<dubbo:reference/>
标签最终会被 Spring IoC 容器转换为 ReferenceConfig 实例,事实上我们也已经接触过此类的实例了,在 DubboNamespaceHandler 中,我们已经初始化了 <dubbo:reference>
标签的解析器:
1
2
3
4
5
|
//DubboNamespaceHandler#init 方法
@Override
public void init() {
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
}
|
其中,ReferenceBean 实际上就是 ReferenceConfig 的子类。
另一方面,ReferenceBean 类实际上又是 FactoryBean 的子类,因此在 DubboNamespaceHandler#init 方法中就已经向 Spring 容器告知:如果其他方法向 IoC 容器索要在 <dubbo:reference/>
标签中的定义的 bean,那么 Spring 容易会将 ReferenceBean 作为具体的 FactoryBean 来生产对应的 Bean 实例。ReferenceBean 会在 ReferenceConfig#createProxy 方法中利用 javassist 进行代理逻辑的注入,创建一个具体的 <dubbo:reference/>
标签对应接口的具体实现类实例。
4. 完成一个自己的的 Spring XML 扩展
本小节参考于:https://juejin.im/post/6844903866593460231,这主要因为我对于 XML 配置并不是很熟悉,主要用于验证可行性。
本项目的开源地址为:https://github.com/Spongecaptain/springxmlsample
通过运行 cool.spongecaptain.springxmlsample.run.Bootstrap#main 方法进行测试。
1.依赖部分
首先创建一个自己的模块,例如 cool.spongecaptain.springxmlsample,并引入 Spring Boot 依赖,如下:
1
2
3
4
5
6
7
8
9
10
11
|
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.3.1.RELEASE</version>
</dependency>
|
2.配置文件部分
然后在 /META-INF 目录下创建 3 个配置文件,如下:
-
resources/META-INF/spongecaptain.handlers:
1
|
http\://www.spongecaptain.cool/schema/spongecaptain=cool.spongecaptain.springxmlsample.handler.SpongecaptainNamespaceHandler
|
-
resources/META-INF/spongecaptain.xsd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.spongecaptain.cool/schema/spongecaptain"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.spongecaptain.cool/schema/spongecaptain">
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:element name="myapplication">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="myservice">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>
|
-
resources/META-INF/spring.schemas
1
|
http\://www.spongecaptain.cool/schema/spongecaptain/spongecaptain.xsd=META-INF/spongecaptain.xsd
|
3.代码部分
首先,我们注册两个简单的 Java POJO:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public class MyApplicationConfig {
private String name;
private String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public String toString() {
return name + " " + value;
}
}
|
以及
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public class MyServiceBean {
private String name;
private String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public String toString() {
return name + " " + value;
}
}
|
接着,我们实现一个 SpongecaptainBeanDefinitionParser 实现类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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;
public class SpongecaptainBeanDefinitionParser implements BeanDefinitionParser {
private final Class<?> beanClass;
public SpongecaptainBeanDefinitionParser(Class<?> beanClass) {
this.beanClass = beanClass;
}
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass) {
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(beanClass);
beanDefinition.setLazyInit(false);
String name = element.getAttribute("name");
beanDefinition.getPropertyValues().addPropertyValue("name", name);
parserContext.getRegistry().registerBeanDefinition(name, beanDefinition);
return beanDefinition;
}
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
return parse(element, parserContext, beanClass);
}
}
|
最后,我们定义一个 NamespaceHandler 具体实现类,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import cool.spongecaptain.springxmlsample.config.MyApplicationConfig;
import cool.spongecaptain.springxmlsample.config.MyServiceBean;
import cool.spongecaptain.springxmlsample.parser.SpongecaptainBeanDefinitionParser;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class SpongecaptainNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
//注意,我们这里使用自己的配置类
super.registerBeanDefinitionParser("myapplication", new SpongecaptainBeanDefinitionParser(MyApplicationConfig.class));
super.registerBeanDefinitionParser("myservice", new SpongecaptainBeanDefinitionParser(MyServiceBean.class));
}
}
|
4.测试运行部分
首先我们创建 XML 配置文件:resources/springcaptain(命名可以是任意的),声明需要注册的两个 bean,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<?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:spongecaptain="http://www.spongecaptain.cool/schema/spongecaptain"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.spongecaptain.cool/schema/spongecaptain
http://www.spongecaptain.cool/schema/spongecaptain/spongecaptain.xsd">
<spongecaptain:myapplication name="spongecaptain-sample-application" value = "bean 1"/>
<spongecaptain:myservice name="spongecaptain-sample-service" value = "bean 2"/>
</beans>
|
然后,创建一个测试,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import cool.spongecaptain.springxmlsample.config.MyApplicationConfig;
import cool.spongecaptain.springxmlsample.config.MyServiceBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ImportResource;
@ImportResource(locations = {"classpath:spongecaptain.xml"})
@SpringBootApplication
public class Bootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(Bootstrap.class, args);
MyServiceBean serviceBean = applicationContext.getBean(MyServiceBean.class);
System.out.println(serviceBean.getName());
MyApplicationConfig applicationConfig = applicationContext.getBean(MyApplicationConfig.class);
System.out.println(applicationConfig.getName());
}
}
|
控制台会输出:
spongecaptai n-sample-service
spongecaptain-sample-application
可见我们的 XML 配置方案已经被 Spring IoC 容器所识别,可以使用了。