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)保持一致。

2.2 META-INF/spring.handlers 配置文件

参考 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

可以看到我们在前面两节提到的配置文件:

image-20201028221232524

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 键值对:

image-20201029203247878

此时 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,整个结果如下图所示:

image-20201029204551925

我们可以发现,点击类型上的 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 方法一切都会变得明了,如下图所示:

image-20201029215001403

其中 <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 容器所识别,可以使用了。