隔离功能扩展

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

下文涉及的概念,说明如下:

  • 扩展点:指暴露给其它模块,可以用来进行扩展的 Bean。

  • 贡献点:扩展点 Bean 中的变量,其值可以被其它模块中的自定义变量覆盖。

  • 贡献点描述类:对贡献点进行描述,并让其暴露给其它模块的类。

  • XMap:是一个库,通过在 Java 对象上使用注解,实现 XML 元素和 Java 对象的映射。该库目前通过 Nuxeo Runtime 进行使用,可以让扩展贡献点的使用更便捷。同时,该库与 Nuxeo Runtime 完全独立,也可以被任意类型的 Java 应用程序使用。

下文内容主要包括下述 2 方面:

  • 实现扩展能力

  • 高级扩展能力实例。实现方式主要有下述 2 种:

    • XML 配置实现

    • 客户端编程 API 实现。

实现扩展能力

下文通过 XML 配置的方式,对如何实现 SOFABoot 的扩展能力进行说明。通过 XML 实现扩展的主要逻辑如下:

  • 扩展点 Bean MyExtensionImpl 通过贡献点描述类 ContributionDescriptor 及其 XML 配置,被注册到 SOFABoot 的 Runtime 里面,成为其一个组件。

  • 程序在对 XML 配置进行解析时,MyExtensionImpl 这个 Bean 中的贡献点 word 变量就被覆盖为 <sofa:content><value> 对应的 Khotyn Huang这个新值了。

XML 配置实现扩展示意图XML 扩展实现

扩展能力的实现,主要包括以下 3 个步骤:

  1. 定义扩展点 Bean

  2. 定义贡献点描述类

  3. 配置扩展

定义扩展点 Bean

下文示例定义了一个 Bean,并对其私有变量 word 进行暴露,作为贡献点,使其能够被其他模块自定义变量覆盖。

定义步骤示例如下:

  1. 创建一个接口:

    public interface MyExtension{
       String say();
    }
  2. 创建该接口的实现:

    public class MyExtensionImpl implements MyExtension {
        private String word;
    
        @Override
        public String say() {
            return word;
        }
    
        public void setWord(String word) {
            this.word = word;
        }
    
        /**
         * 只设置了一个贡献点,该方法还比较简单,随着贡献点增多,方法会变复杂。
         */
        public void registerExtension(Extension extension) {
            Object[] contributions = extension.getContributions();
            String extensionPoint = extension.getExtensionPoint();
    
            setWord(((ContributionDescriptor) contributions[0]).getValue());
        }
    }

    方法说明

    关于方法 registerExtension(),说明如下:

    • 其为组件方法,SOFABoot 框架将会调用这个方法进行扩展点设置,必不可少。

    • 所传递参数 Extension 为 SOFABoot 提供的扩展点描述类。

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

    <bean id="myExtension" class="com.alipay.helloword.biz.service.impl.MyExtensionImpl">
         <property name="word" value="Hello, world"/>
    </bean>

定义贡献点描述类

定义步骤示例如下:

  1. 创建贡献点描述类:

    @XObject("word")
    public class ContributionDescriptor{
        @XNode("value")
        private String value;
    
        public String getValue(){
            return value;
        }
    }
  2. 在 XML 中配置该贡献点描述类:

    <sofa:extension-point name="MyExtensionPoint" ref="myExtension">
            <sofa:object class="com.alipay.helloword.biz.service.ContributionDescriptor"/>
    </sofa:extension-point>

    字段说明

    • name: 为扩展点的名字。

    • ref: 为扩展点 bean 的 id。

    • <sofa:object> 子元素:定义的是扩展点的贡献点(Contribution),可以定义多个贡献点。

    • class 属性:对应的是贡献点描述类,是对贡献点的描述,这种描述是通过 XMap 的方式来进行的,可以将 Java 对象和 XML 文件进行映射。

配置扩展

通过对 xml 的配置来实现对隔离功能的扩展。示例如下:

<sofa:extension bean="myExtension" point="MyExtensionPoint">
    <sofa:content>
        <!-- 这里 word 对应于 ContributionDescriptor 中的 @XObject 后面声明的字符串 -->
        <word>
            <!-- 这里 value 对应于 ContributionDescriptor 中的 @XNode 后面声明的字符串-->
            <value>Khotyn Huang</value>
        </word>
    </sofa:content>
</sofa:extension>

字段说明

  • word: 对应于 ContributionDescriptor 中 @XObject 后面声明的字符串。

  • value:对应于 ContributionDescriptor 中的 @XNode 后面声明的字符串。

  • bean:为扩展点 bean。

  • point:为扩展点的名字。

  • <sofa:content>: 里面的内容为扩展内容的定义,其会通过 XMap 将内容解析为扩展描述类对象,此处即为 com.alipay.sofa.boot.test.extension.ExtensionDescriptor 对象。

高级扩展能力实例

SOFABoot 的扩展能力包括:

  • 支持 XMap 原生描述能力,包括:

    • List

    • Map

  • 扩展了 Spring 集成能力,包括:

    • 通过 XNode 扩展出了 XNodeSpring

    • 通过 XNodeList 扩展出了 XNodeListSpring

    • 通过 XNodeMap 扩展出了 XNodeMapSpring

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

下文以使用 XNodeListSpring 为例,对上述高级扩展功能进行说明。实现方式有 2 种:

  • XML 配置实现

  • 客户端编程 API 实现

XML 配置实现

主要步骤如下:

  1. 定义扩展点 Bean

  2. 定义贡献点描述类

  3. 配置扩展

定义扩展点 Bean

定义步骤示例如下:

  1. 创建一个接口:

    package com.alipay.sofa.boot.test;
    
    public interface IExtension{
    
         List<SimpleSpringListBean> getSimpleSpringListBeans();
    }
    说明

    • 本接口返回一个 list,目标是这个 list 能够通过扩展的方式填充;

    • SimpleSpringListBean 可根据需求来定义,此处假设定义了一个空实现。

  2. 创建该接口实现类:

    public class IExtensionImpl implements IExtension {
        private List<SimpleSpringListBean> simpleSpringListBeans = new ArrayList<>();
    
        @Override
        public List<SimpleSpringListBean> getSimpleSpringListBeans() {
            return simpleSpringListBeans;
        }
    
        public void registerExtension(Extension extension) throws Exception {
            Object[] contributions = extension.getContributions();
            String extensionPoint = extension.getExtensionPoint();
    
            if (contributions == null) {
                return;
            }
    
            for (Object contribution : contributions) {
                if ("testSpringList".equals(extensionPoint)) {
                    simpleSpringListBeans.addAll(((SpringListContributionDescriptor) contribution)
                            .getValues());
                }
            }
        }
    }
    
    
    说明

    simpleSpringListBeans 为贡献点。

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

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

定义贡献点描述类

定义步骤如下:

  1. 创建一个贡献点描述类:

    @XObject("testSpringList")
    public class SpringListContributionDescriptor {
    
        @XNodeListSpring(value = "value", componentType = SimpleSpringListBean.class, type = ArrayList.class)
        private List<SimpleSpringListBean> values;
    
        public List<SimpleSpringListBean> getValues() {
            return values;
        }
    }     
    说明

    @XNodeListSpring:当在 XML 中配置相应 bean 的名字时, SOFABoot 会从 Spring 上下文中获取到相应的 bean 实例。

  2. 在 XML 中定义该贡献点描述类:

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

配置扩展

通过对 xml 的配置来实现对隔离功能的扩展。

<bean id="simpleSpringListBean1" class="com.alipay.sofa.runtime.integration.extension.bean.SimpleSpringListBean"/>
<bean id="simpleSpringListBean2" class="com.alipay.sofa.runtime.integration.extension.bean.SimpleSpringListBean"/>

<sofa:extension bean="iExtension" point="testSpringList">
    <sofa:content>
        <testSpringList>
            <value>simpleSpringListBean1</value>
            <value>simpleSpringListBean2</value>
        </testSpringList>
    </sofa:content>
</sofa:extension>
说明

对配置内容说明如下:

  • 定义了两个 bean,并将其放入扩展定义中。

  • 调用 iExtensiongetSimpleSpringListBeans 方法,将会看到其中包含了通过扩展方式添加的两个 bean。

客户端编程 API 实现

主要通过 com.alipay.sofa.runtime.api.client.ExtensionClient 来完成下述步骤:

  1. 获取扩展客户端 Bean

  2. 设置扩展点参数

  3. 定义扩展

获取扩展客户端 Bean

需要实现 SOFABoot 中提供的接口 com.alipay.sofa.runtime.api.aware.ExtensionClientAware,示例如下:

public class ExtensionClientBean implements ExtensionClientAware {

    private ExtensionClient extensionClient;

    @Override
    public void setExtensionClient(ExtensionClient extensionClient) {
        this.clientFactory = extensionClient;
    }

    public ExtensionClient getClientFactory() {
        return extensionClient;
    }
}

设置扩展点参数

ExtensionPointParam extensionPointParam =new ExtensionPointParam();
extensionPointParam.setName("clientValue");
extensionPointParam.setTargetName("iExtension");
extensionPointParam.setTarget(iExtension);
extensionPointParam.setContributionClass(ClientExtensionDescriptor.class);
extensionClient.publishExtensionPoint(extensionPointParam);

通过客户端定义扩展点与通过 XML 时各参数的含义保持一致:

  • name 为扩展点的名字。

  • targetName 为扩展点所作用在的 bean 的名字。

  • target 为扩展点所作用在的 bean。

  • contributionClass 为扩展点的贡献点具体的描述,此处描述的定义示例如下:

@XObject("clientValue")
public class ClientExtensionDescriptor{
    @XNode("value")
    private String value;

    public String getValue(){
        return value;
    }
}

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

定义扩展

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

通过客户端定义扩展与通过 XML 时各参数的含义保持一致:

  • targetInstanceName 为扩展所作用在的 bean。

  • targetName 为扩展点的名字。

  • element 里面的内容为扩展的定义,此处需要传入 Element 对象,通过从 XML 中读取,示例内容如下:

<clientValue>
    <value>SOFABoot Extension Client Test</value>
</clientValue>

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

客户端限制

由于扩展的定义强依赖 XML,因此虽然此处通过客户端发布扩展点和扩展,但是扩展自身的内容还是需要 XML 来描述,并没有真正做到只使用客户端。