全部产品
云市场

SOFABoot 扩展点

更新时间:2019-09-05 19:38:13

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

使用扩展点能力

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

  1. 定义提供扩展能力的 bean
  2. 定义扩展点
  3. 定义扩展

定义提供扩展能力的 bean

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

  1. 定义一个接口:

    1. package com.alipay.sofa.boot.test;
    2. public interface IExtension {
    3. String say();
    4. }
  2. 定义该接口的实现:

    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,详见下文 定义扩展

  3. 在模块的 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. 需要一个类去描述该扩展点:

    1. @XObject("word")
    2. public class ExtensionDescriptor {
    3. @XNode("value")
    4. private String value;
    5. public String getValue() {
    6. return value;
    7. }
    8. }
  2. 在 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 文件进行映射)。

定义扩展

上述已定义好扩展点,此时可对这个 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 对象。

SOFABoot 在解析到贡献点时,会调用被扩展 bean 的 registerExtension 方法,即 com.alipay.sofa.boot.test.impl.ExtensionImpl 中定义的 registerExtension 方法,其中包含了用户定义的贡献点处理逻辑。

上述的示例中,获取用户定义的 value 值,并将其设置到 word 字段中,覆盖 bean 中原始定义的值。此时,调用 extension bean 的 say() 方法,可以看到返回扩展中定义的值为 newValue 。

XMap 支持和扩展

上述的示例仅是一个很简单的扩展,其实 XMap 包含了非常丰富的描述能力,包括 List, Map 等。

在 SOFABoot 中,除了 XMap 原生的支持以外,还扩展了 Spring 集成的能力:

  • 通过 XNode 扩展出了 XNodeSpring
  • 通过 XNodeList 扩展出了 XNodeListSpring
  • 通过 XNodeMap 扩展出了 XNodeMapSpring

这部分的扩展能力,使扩展点的能力更加丰富,描述对象中可以直接指向一个 SpringBean。用户配置 bean 的名字,SOFABoot 会根据名字从 Spring 上下文中获取到 bean。

此处,以使用 XNodeListSpring 为例,依然是上述的三个步骤:

定义提供扩展能力的 bean

  1. 定义接口:

    1. package com.alipay.sofa.boot.test;
    2. public interface IExtension {
    3. List<SimpleSpringListBean> getSimpleSpringListBeans();
    4. }
    • 本接口中,返回一个 list,目标是这个 list 能够被通过扩展的方式填充;
    • 其中,SimpleSpringListBean 可根据需求来定义,此处假设定义了一个空实现。
  2. 定义实现:

    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 的能力。

  3. 在模块的 Spring 配置文件中,配置这个 bean:

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

定义扩展点

  1. 需要一个对象去描述该扩展点:

    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 实例。

  2. 在 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. }

因此,获取 ExtensionClient,只需将 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 来描述,并没有真正做到只使用客户端。