SOFABoot supports modular isolation. But in actual usage scenarios, There is one case that beans in one module sometimes need to open some entries for another module to expand. SOFABoot draws on and uses the Nuxeo Runtime project and the nuxeo project and expands on it, provides the ability to extend points with Spring, We call it Extension Point
.
Usage
Using extension point capabilities in SOFABoot requires the following three steps:
Define a bean that provides extension capabilities
When using the SOFABoot extension point capability, you first need to define an interface that needs to be extended, like:
package com.alipay.sofa.boot.test;
public interface IExtension {
String say();
}
Define the implementation of this interface:
package com.alipay.sofa.boot.test.impl;
public class ExtensionImpl implements IExtension {
private String word;
@Override
public String say() {
return word;
}
public void setWord(String word) {
this.word = word;
}
public void registerExtension(Extension extension) throws Exception {
Object[] contributions = extension.getContributions();
String extensionPoint = extension.getExtensionPoint();
if (contributions == null) {
return;
}
for (Object contribution : contributions) {
if ("word".equals(extensionPoint)) {
setWord(((ExtensionDescriptor) contribution).getValue());
}
}
}
}
Here you can see that there is a method: registerExtension
, you can temporarily ignore this method, and later will introduce its specific role.
In the module’s Spring configuration file, we add configuration of this bean:
<bean id="extension" class="com.alipay.sofa.boot.test.impl.ExtensionImpl">
<property name="word" value="Hello, world"/>
</bean>
Defining extension points
There is a field word in the above bean. In practice, we want this field to be overridden by other module customizations. Here we expose it as an extension point.
First, you need a class to describe this extension point:
@XObject("word")
public class ExtensionDescriptor {
@XNode("value")
private String value;
public String getValue() {
return value;
}
}
Then define the extension point in xml:
<sofa:extension-point name="word" ref="extension">
<sofa:object class="com.alipay.sofa.boot.test.extension.ExtensionDescriptor"/>
</sofa:extension-point>
among them:
- name
is the name of the extension point
- ref
is the bean to which the extension point is applied
- object
is a concrete description of the contribution point of the extension point. This description is done by XMap (XMap is used to map Java objects and XML files. It is recommended to search XMap documents on the Internet to understand XMap)
Defining extension implements
The above has defined the extension point, and we can extend this bean at this point:
<sofa:extension bean="extension" point="word">
<sofa:content>
<word>
<value>newValue</value>
</word>
</sofa:content>
</sofa:extension>
among them:
- bean
is the bean to which the extension is applied
- point
is the name of the extension point
- The content inside content
is an extension definition, which will parse the content through XMap into: the extension point’s contribution point specific description object, here is the com.alipay.sofa.boot.test.extension.ExtensionDescriptor
object
At this point, we can look back at the registerExtension
method defined in com.alipay.sofa.boot.test.impl.ExtensionImpl
. When SOFABoot resolves to the contribution point, it will call the registerExtension of the extended bean. The
method contains the user-defined contribution point processing logic. In the above example, the user-defined value is obtained and set to the value in the word field that overrides the original definition in the bean.
At this point, call the extension
bean’s say()
method, and you can see the value defined in the return extension: newValue.
XMap Support and Extension
The above example is just a very simple extension. In fact, XMap contains a very rich description capability, including List
, Map
, etc. These can be seen by looking at the XMap documentation.
In SOFABoot, in addition to XMap native support, it extends the ability to integrate with Spring:
- Extend XNodeSpring
via XNode
- Extend XNodeListSpring
via XNodeList
- Extend XNodeMapSpring
via XNodeMap
This part of the expansion capabilities, the ability to extend the extension point, the description object can directly point to a SpringBean (user configuration bean name, SOFABoot will get the bean from the spring context according to the name), here is a use of XNodeListSpring
The example is still the three steps described above:
Defining a bean that provides extended capabilities
In this interface definition, we return a list, the goal is that this list can be filled by extension:
package com.alipay.sofa.boot.test;
public interface IExtension {
List<SimpleSpringListBean> getSimpleSpringListBeans();
}
Where SimpleSpringListBean
can be defined according to requirements, here we assume that an empty return is defined.
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(((SpringListExtensionDescriptor) contribution)
.getValues());
}
}
}
}
You can see that the bean defined by the extension point is added to the list in registerExtension
to achieve the ability for the user to extend the list.
In the module’s Spring configuration file, we configure this bean:
<bean id="iExtension" class="com.alipay.sofa.runtime.integration.extension.bean.IExtensionImpl"/>
Defining extension points
First, you need an object to describe:
@XObject("testSpringList")
public class SpringListExtensionDescriptor {
@XNodeListSpring(value = "value", componentType = SimpleSpringListBean.class, type = ArrayList.class)
private List<SimpleSpringListBean> values;
public List<SimpleSpringListBean> getValues() {
return values;
}
}
Here we use @XNodeListSpring
. When the name of the corresponding bean is configured in XML, SOFABoot gets the corresponding bean instance from the spring context.
Define this extension point in XML
<sofa:extension-point name="testSpringList" ref="iExtension">
<sofa:object class="com.alipay.sofa.runtime.integration.extension.descriptor.SpringListExtensionDescriptor"/>
</sofa:extension-point>
Defining extensions
<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>
When defining an extension, first define two beans and then put them into the extension definition.
At this point, call iExtension.getSimpleSpringListBeans
method, you can see that it contains two beans added by the extension.
Defining extension points and extensions by client mode
In addition to defining extension points and extensions through the above xml configuration, SOFABoot also provides com.alipay.sofa.runtime.api.client.ExtensionClient
to define extension points and extensions.
Get ExtensionClient
Getting com.alipay.sofa.runtime.api.client.ExtensionClient
is relatively simple, and com.alipay.sofa.runtime.api.aware.ExtensionClientAware
is provided in SOFABoot:
public class ExtensionClientBean implements ExtensionClientAware {
private ExtensionClient extensionClient;
@Override
public void setExtensionClient(ExtensionClient extensionClient) {
this.clientFactory = extensionClient;
}
public ExtensionClient getClientFactory() {
return extensionClient;
}
}
Define ExtensionClientBean
as a Spring bean.
定义扩展点
ExtensionPointParam extensionPointParam = new ExtensionPointParam();
extensionPointParam.setName("clientValue");
extensionPointParam.setTargetName("iExtension");
extensionPointParam.setTarget(iExtension);
extensionPointParam.setContributionClass(ClientExtensionDescriptor.class);
extensionClient.publishExtensionPoint(extensionPointParam);
The meaning of each parameter is consistent when the client and the extension point are registered via XML:
among them:
- name
is the name of the extension point
- targetName
is the name of the bean to which the extension point is applied
- target
is the bean to which the extension point is applied
- contributionClass
is a concrete description of the contribution points of the extension point. The definitions described here are as follows:
@XObject("clientValue")
public class ClientExtensionDescriptor {
@XNode("value")
private String value;
public String getValue() {
return value;
}
}
Define extension points by publishExtensionPoint
of com.alipay.sofa.runtime.api.client.ExtensionClient
Defining extension implements
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);
The meaning of each parameter is consistent when the client and the extension point are registered via XML:
- targetInstanceName
is the bean to which the extension is applied
- targetName
is the name of the extension point
- The content inside the element
is an extended definition. Here you need to pass in the Element
object. By reading it from XML, the contents of the example are as follows:
<clientValue>
<value>SOFABoot Extension Client Test</value>
</clientValue>
The extension can be defined by com.alipay.sofa.runtime.api.client.ExtensionClient.publishExtension()
Restrictions
It can be seen that since the definition of the extension is strongly dependent on XML, although the extension point and extension are released through the client, the content of the extension itself needs XML to describe, and does not really use only the client. This part is welcome to contribute.