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 來描述,并沒有真正做到只使用客戶端。