Ark 容器和 Ark Plugin 在运行时由不同的类加载器加载,不能使用常规的 ServiceLoader 提供 SPI 扩展,所以 SOFAArk 自定义扩展点 SPI 机制,Ark Plugin 实现 SPI 机制。
因为 Biz 卸载问题,Ark Biz 暂时不支持该 SPI 机制,只适用于 Ark Plugin 之间。
声明扩展接口
使用注解@Extensible
声明扩展接口,注解定义如下:
@Target({ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Extensible{
/**
* return specify extensible file name, default value is the
* full name of interface.
*/
String file() default"";
/**
* return whether this a singleton, with a single, shared instance
* returned on all calls, default value is true.
*/
boolean singleton() default true;
}
file
:用于声明 SPI 扩展文件名,默认为接口全类名。singleton
:用于声明加载扩展类是否为单例模式。
声明扩展实现
使用注解@Extension
声明扩展实现,注解定义如下:
@Target({ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Extension{
/**
* extension name
*/
String value();
/**
* extension order, Higher values are interpreted as lower priority.
* As a consequence, the object with the lowest value has the highest
* priority.
*/
int order() default 100;
}
value
:用于定义扩展实现名称,例如不同的插件扩展同一个接口,可能会取不同的名字。order
:用于决定具体扩展实现的生效顺序。运行时,对于同一个接口的扩展实现生效规则如下:
规则一:名称相同的扩展实现,只会返回优先级高的扩展实现类,order 数字越小,优先级越高。
规则二:名称不相同的扩展实现,则返回一个对应的 List 列表,每个名称返回优先级最高的扩展实现。
加载 SPI 实现类
正常情况下,使用 ServiceLoader 加载 SPI 接口实现。SOFAArk 提供了工具类ArkServiceLoader
用于加载扩展实现,工具类定义了两个简单的方法:
方法一:用于加载指定接口和名称的扩展实现,返回单个结果。参考上述规则一。
方法二:用于加载指定接口的扩展实现,返回列表结果。参考上述规则二。
示例代码如下:
public class ArkServiceLoader{
private static ExtensionLoaderService extensionLoaderService;
// 方法一
public static<T> T loadExtension(Class<T> interfaceType,String extensionName){
return extensionLoaderService.getExtensionContributor(interfaceType, extensionName);
}
// 方法二
public static<T>List<T> loadExtension(Class<T> interfaceType){
return extensionLoaderService.getExtensionContributor(interfaceType);
}
}
定义 SPI 接口的插件需要导出该接口,负责实现 SPI 接口的插件需要导入该接口。SOFAArk 容器本身也会定义部分用于插件扩展实现的 SPI 接口,例如
ClassLoaderHook
。因为 Biz 会动态地安装和卸载,如果支持 Biz 的扩展实现加载,生命周期容易引起混乱,所以暂时不考虑支持 Biz 的 SPI 扩展实现加载。如果确实存在 Ark Plugin 需要主动触发 Ark Biz 的逻辑调用,可以通过 SOFAArk 内部事件机制。
SOFAArk 默认扩展点
SOFAArk 容器目前提供了唯一一个扩展点ClassLoaderHook
,用于其他插件提供扩展实现、自定义类或资源加载逻辑。
ClassLoaderHooker
接口用于扩展 BizClassLoader 和 PluginClassLoader 类(资源)加载逻辑,定义如下:
@Extensible
public interface ClassLoaderHook<T>{
Class<?> preFindClass(String name,ClassLoaderService classLoaderService, T t)
throws ClassNotFoundException;
Class<?> postFindClass(String name,ClassLoaderService classLoaderService, T t)
throws ClassNotFoundException;
URL preFindResource(String name,ClassLoaderService classLoaderService, T t);
URL postFindResource(String name,ClassLoaderService classLoaderService, T t);
Enumeration<URL> preFindResources(String name,ClassLoaderService classLoaderService, T t)
throws IOException;
Enumeration<URL> postFindResources(String name,ClassLoaderService classLoaderService, T t)
throws IOException;
}
通过在插件中扩展该 SPI 接口实现,可以自定义 PluginClassLoader 和 BizClassLoader 的类或资源的加载逻辑。
扩展实现 PluginClassLoader 加载逻辑
定义对 PluginClassLoader 的扩展实现需要指定 extension 名为plugin-classloader-hook
,这是因为目前 SOFAArk 的策略只允许一个 Plugin ClassLoaderHook 扩展实现生效。如果同时定义多个扩展类,优先级最高的生效。
@Extension("plugin-classloader-hook")
public class TestPluginClassLoaderHook implements ClassLoaderHook<Plugin>{
}
扩展实现 BizClassLoader 加载逻辑
定义对 BizClassLoader 的扩展实现需要指定 extension 名为 biz-classloader-hook
,原因与定义对 PluginClassLoader 的扩展实现一样。目前 SOFAArk 的策略只允许一个Biz ClassLoaderHook 扩展实现生效,如果同时定义多个扩展类,优先级最高的生效。
@Extension("biz-classloader-hook")
public class TestBizClassLoaderHook implements ClassLoaderHook<Biz>{
}