SOFABoot 支持模块化隔离,在实际的使用场景中,一个模块中的 bean 有时候需要开放一些入口,供另外一个模块扩展。SOFABoot 借鉴和使用了 Nuxeo Runtime 项目 以及 nuxeo 项目,并在上面扩展,与 Spring 融合,提供扩展点的能力。

使用

在 SOFABoot 中使用扩展点能力,需要以下三个步骤:

定义提供扩展能力的 bean

在使用 SOFABoot 扩展点能力时,首先需要定一个需要被扩展的 bean,先定一个接口:

  1. package com.alipay.sofa.boot.test;
  2. public interface IExtension {
  3. String say();
  4. }

定义这个接口的实现:

  1. package com.alipay.sofa.boot.test.impl;
  2. public class ExtensionImpl implements IExtension {
  3. private String word;
  4. @Override
  5. public String say() {
  6. return word;
  7. }
  8. public void setWord(String word) {
  9. this.word = word;
  10. }
  11. public void registerExtension(Extension extension) throws Exception {
  12. Object[] contributions = extension.getContributions();
  13. String extensionPoint = extension.getExtensionPoint();
  14. if (contributions == null) {
  15. return;
  16. }
  17. for (Object contribution : contributions) {
  18. if ("word".equals(extensionPoint)) {
  19. setWord(((ExtensionDescriptor) contribution).getValue());
  20. }
  21. }
  22. }
  23. }

在这里可以看到有一个方法:registerExtension ,暂时可以先不用管这个方法,后续会介绍其具体的作用。

在模块的 Spring 配置文件中,我们把这个 bean 给配置起来:

  1. <bean id="extension" class="com.alipay.sofa.boot.test.impl.ExtensionImpl">
  2. <property name="word" value="Hello, world"/>
  3. </bean>

定义扩展点

在上面的 bean 中有一个字段 word ,在实际中,我们希望这个字段能够被其他的模块自定义进行覆盖,这里我们将其以扩展点的形式暴露出来。

首先需要一个类去描述这个扩展点:

  1. @XObject("word")
  2. public class ExtensionDescriptor {
  3. @XNode("value")
  4. private String value;
  5. public String getValue() {
  6. return value;
  7. }
  8. }

然后在 xml 中定义扩展点:

  1. <sofa:extension-point name="word" ref="extension">
  2. <sofa:object class="com.alipay.sofa.boot.test.extension.ExtensionDescriptor"/>
  3. </sofa:extension-point>

其中:- name 为扩展点的名字- ref 为扩展点所作用在的 bean- object 为扩展点的贡献点具体的描述,这个描述是通过 XMap 的方式来进行的(XMap 的作用是将 Java 对象和 XML 文件进行映射,这里建议通过在网上搜索下 XMap 的文档来了解 XMap)

定义扩展

上述已经将扩展点定义好了,此时我们就可以对这个 bean 进行扩展了:

  1. <sofa:extension bean="extension" point="word">
  2. <sofa:content>
  3. <word>
  4. <value>newValue</value>
  5. </word>
  6. </sofa:content>
  7. </sofa:extension>

其中:- bean 为扩展所作用在的 bean- point 为扩展点的名字- content 里面的内容为扩展的定义,其会通过 XMap 将内容解析为:扩展点的贡献点具体的描述对象,在这里即为 com.alipay.sofa.boot.test.extension.ExtensionDescriptor 对象

到这里,我们可以回头看一开始在 com.alipay.sofa.boot.test.impl.ExtensionImpl 中定义的 registerExtension 方法了,SOFABoot 在解析到贡献点时,会调用被扩展 bean 的 registerExtension 方法,其中包含了用户定义的贡献点处理逻辑,在上述的例子中,获取用户定义的 value 值,并将其设置到 word 字段中覆盖 bean 中原始定义的值。

此时,调用 extension bean 的 say() 方法,可以看到返回扩展中定义的值: newValue 。

XMap 支持和扩展

上述的例子中只是一个很简单的扩展,其实 XMap 包含了非常丰富的描述能力,包括 List, Map 等,这些可以通过查看 XMap 的文档来了解。

在 SOFABoot 中,除了 XMap 原生的支持以外,还扩展了跟 Spring 集成的能力:- 通过 XNode 扩展出了 XNodeSpring- 通过 XNodeList 扩展出了 XNodeListSpring- 通过 XNodeMap 扩展出了 XNodeMapSpring

这部分的扩展能力,让扩展点的能力更加丰富,描述对象中可以直接指向一个 SpringBean(用户配置 bean 的名字,SOFABoot 会根据名字从 spring 上下文中获取到 bean),这里举一个使用 XNodeListSpring 的例子,依然是上述描述的三个步骤:

定义提供扩展能力的 bean

接口定义:

在这个接口里,返回一个 list,目标是这个 list 能够被通过扩展的方式填充

  1. package com.alipay.sofa.boot.test;
  2. public interface IExtension {
  3. List<SimpleSpringListBean> getSimpleSpringListBeans();
  4. }

其中 SimpleSpringListBean 可根据需求来定义,这里我们假设定义了一个空实现

实现:

  1. public class IExtensionImpl implements IExtension {
  2. private List<SimpleSpringListBean> simpleSpringListBeans = new ArrayList<>();
  3. @Override
  4. public List<SimpleSpringListBean> getSimpleSpringListBeans() {
  5. return simpleSpringListBeans;
  6. }
  7. public void registerExtension(Extension extension) throws Exception {
  8. Object[] contributions = extension.getContributions();
  9. String extensionPoint = extension.getExtensionPoint();
  10. if (contributions == null) {
  11. return;
  12. }
  13. for (Object contribution : contributions) {
  14. if ("testSpringList".equals(extensionPoint)) {
  15. simpleSpringListBeans.addAll(((SpringListExtensionDescriptor) contribution)
  16. .getValues());
  17. }
  18. }
  19. }
  20. }

可以看到:在 registerExtension 中将通过贡献点定义的 bean 加入到 list 中,以达到让用户扩展这个 list 的能力。

在模块的 Spring 配置文件中,我们把这个 bean 给配置起来:

  1. <bean id="iExtension" class="com.alipay.sofa.runtime.integration.extension.bean.IExtensionImpl"/>

定义扩展点

首先需要一个对象去描述:

  1. @XObject("testSpringList")
  2. public class SpringListExtensionDescriptor {
  3. @XNodeListSpring(value = "value", componentType = SimpleSpringListBean.class, type = ArrayList.class)
  4. private List<SimpleSpringListBean> values;
  5. public List<SimpleSpringListBean> getValues() {
  6. return values;
  7. }
  8. }

在这里用到了 @XNodeListSpring ,当在 xml 中配置相应 bean 的名字时, SOFABoot 会从 spring 上下文中获取到相应的 bean 实例

在 xml 中将这个扩展点定义出来

  1. <sofa:extension-point name="testSpringList" ref="iExtension">
  2. <sofa:object class="com.alipay.sofa.runtime.integration.extension.descriptor.SpringListExtensionDescriptor"/>
  3. </sofa:extension-point>

定义扩展

  1. <bean id="simpleSpringListBean1" class="com.alipay.sofa.runtime.integration.extension.bean.SimpleSpringListBean" />
  2. <bean id="simpleSpringListBean2" class="com.alipay.sofa.runtime.integration.extension.bean.SimpleSpringListBean" />
  3. <sofa:extension bean="iExtension" point="testSpringList">
  4. <sofa:content>
  5. <testSpringList>
  6. <value>simpleSpringListBean1</value>
  7. <value>simpleSpringListBean2</value>
  8. </testSpringList>
  9. </sofa:content>
  10. </sofa:extension>

在定义扩展时,首先定义了两个 bean,然后将它们放入扩展定义中。

此时,调用 iExtensiongetSimpleSpringListBeans 方法,可以看到其中包含了通过扩展方式添加的两个 bean。

通过客户端方式定义扩展点和扩展

除了通过上述 xml 的方式配置定义扩展点和扩展以外,SOFABoot 中还提供了 com.alipay.sofa.runtime.api.client.ExtensionClient 的方式来定义扩展点和扩展

获取 ExtensionClient

获取 com.alipay.sofa.runtime.api.client.ExtensionClient 比较简单,SOFABoot 中提供了 com.alipay.sofa.runtime.api.aware.ExtensionClientAware

  1. public class ExtensionClientBean implements ExtensionClientAware {
  2. private ExtensionClient extensionClient;
  3. @Override
  4. public void setExtensionClient(ExtensionClient extensionClient) {
  5. this.clientFactory = extensionClient;
  6. }
  7. public ExtensionClient getClientFactory() {
  8. return extensionClient;
  9. }
  10. }

ExtensionClientBean 定义为 spring bean 即可

定义扩展点

  1. ExtensionPointParam extensionPointParam = new ExtensionPointParam();
  2. extensionPointParam.setName("clientValue");
  3. extensionPointParam.setTargetName("iExtension");
  4. extensionPointParam.setTarget(iExtension);
  5. extensionPointParam.setContributionClass(ClientExtensionDescriptor.class);
  6. extensionClient.publishExtensionPoint(extensionPointParam);

通过客户端和通过 xml 注册扩展点时各参数的含义保持一致:其中:- name 为扩展点的名字- targetName 为扩展点所作用在的 bean 的名字- target 为扩展点所作用在的 bean- contributionClass 为扩展点的贡献点具体的描述,这里描述的定义示例如下:

  1. @XObject("clientValue")
  2. public class ClientExtensionDescriptor {
  3. @XNode("value")
  4. private String value;
  5. public String getValue() {
  6. return value;
  7. }
  8. }

通过 com.alipay.sofa.runtime.api.client.ExtensionClientpublishExtensionPoint 即可定义扩展点

定义扩展

  1. DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
  2. DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
  3. Document doc = dBuilder.parse(new File(Thread.currentThread().getContextClassLoader()
  4. .getResource("META-INF/extension/extension.xml").toURI()));
  5. ExtensionParam extensionParam = new ExtensionParam();
  6. extensionParam.setTargetName("clientValue");
  7. extensionParam.setTargetInstanceName("iExtension");
  8. extensionParam.setElement(doc.getDocumentElement());
  9. extensionClient.publishExtension(extensionParam);

通过客户端和通过 xml 注册扩展点时各参数的含义保持一致:- targetInstanceName 为扩展所作用在的 bean- targetName 为扩展点的名字- element 里面的内容为扩展的定义,这里需要传入 Element 对象,通过从 xml 中读取,这里示例子的内容如下:

  1. <clientValue>
  2. <value>SOFABoot Extension Client Test</value>
  3. </clientValue>

通过 com.alipay.sofa.runtime.api.client.ExtensionClientpublishExtension 即可定义扩展

客户端限制

可以看到由于扩展的定义强依赖 XML ,故而虽然这里是通过客户端发布扩展点和扩展,但是扩展自身的内容还是需要 XML 来描述,并没有真正做到只使用客户端,此部分欢迎有兴趣的同学提改进意见。