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 配置实现扩展示意图
扩展能力的实现,主要包括以下 3 个步骤:
定义扩展点 Bean
定义贡献点描述类
配置扩展
定义扩展点 Bean
下文示例定义了一个 Bean,并对其私有变量 word 进行暴露,作为贡献点,使其能够被其他模块自定义变量覆盖。
定义步骤示例如下:
创建一个接口:
public interface MyExtension{ String say(); }
创建该接口的实现:
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 提供的扩展点描述类。
在模块的 xml 配置文件中,配置这个 bean:
<bean id="myExtension" class="com.alipay.helloword.biz.service.impl.MyExtensionImpl"> <property name="word" value="Hello, world"/> </bean>
定义贡献点描述类
定义步骤示例如下:
创建贡献点描述类:
@XObject("word") public class ContributionDescriptor{ @XNode("value") private String value; public String getValue(){ return value; } }
在 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 配置实现
主要步骤如下:
定义扩展点 Bean
定义贡献点描述类
配置扩展
定义扩展点 Bean
定义步骤示例如下:
创建一个接口:
package com.alipay.sofa.boot.test; public interface IExtension{ List<SimpleSpringListBean> getSimpleSpringListBeans(); }
说明本接口返回一个 list,目标是这个 list 能够通过扩展的方式填充;
SimpleSpringListBean
可根据需求来定义,此处假设定义了一个空实现。
创建该接口实现类:
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
为贡献点。在模块的 Spring 配置文件中,配置这个 bean:
<bean id="iExtension" class="com.alipay.sofa.runtime.integration.extension.bean.IExtensionImpl"/>
定义贡献点描述类
定义步骤如下:
创建一个贡献点描述类:
@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 实例。在 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,并将其放入扩展定义中。
调用
iExtension
的getSimpleSpringListBeans
方法,将会看到其中包含了通过扩展方式添加的两个 bean。
客户端编程 API 实现
主要通过 com.alipay.sofa.runtime.api.client.ExtensionClient
来完成下述步骤:
获取扩展客户端 Bean
设置扩展点参数
定义扩展
获取扩展客户端 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.ExtensionClient
的 publishExtensionPoint
即可定义扩展点。
定义扩展
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.ExtensionClient
的 publishExtension
即可定义扩展。
客户端限制
由于扩展的定义强依赖 XML,因此虽然此处通过客户端发布扩展点和扩展,但是扩展自身的内容还是需要 XML 来描述,并没有真正做到只使用客户端。