SPI扩展及其源码
原创大约 7 分钟
SPI扩展及其源码
dubbo的扩展点实现与JAVA中实现的扩展点类似,但是功能比JAVA提供的会强大一些。 本次文章简单记录Dubbo SPI扩展点的使用以及核心原理,通过对源码的解读学习dubbo spi扩展点的核心实现以及内部的使用。
SPI 扩展点的实现,都是基于接口来实现的,所以扩展点都是通过实现某个接口来进行扩展的。
核心对象(ExtensionLoader)
ExtensionLoader表示某个接口的扩展点加载器,可以用来加载某个扩展点实例。
核心属性
- extensionInstances:用来缓存某个接口类型所对应的ExtensionLoader实例
- type:表示当前ExtensionLoader实例是那个接口的扩展点加载器
- objectFactory:扩展点工厂,可以通过工厂获取某个扩展点的具体对象实例
使用
- 新建maven工程,并引入dubbo相关依赖
- 在resource包下创建子包:MEAT-INF.dubbo
- 在dubbo包下创建扩展点文件,文件名为扩展点的全类名(包含包名和接口名)
- 在文件内填写扩展点实现类的全类名以及映射关系
当以上步骤都完成以后,在启动类main方法中通过dubbo的扩展点加载器来加载扩展点
// 获取加载扩展点加载器
ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
// 获取扩展点实现
Car car = extensionLoader.getExtension("red");
System.out.println(car);
源码
扩展点加载器
getExtensionLoader(Class class);
实现的是通过某个扩展点,获取该扩展点的扩展点加载器
- 先查询缓存中有没有传入的扩展点的扩展点加载器,有直接返回
- 校验该扩展点是否有scope应用范围缓存,没有就从@SPI注解中获取,默认是范围是APPLICATION
- 判断有没有父扩展点控制器,有的话通过父控制器创建
获取扩展点
T getExtension(String name);
@SuppressWarnings("unchecked")
public T getExtension(String name, boolean wrap) {
// 判断扩展点控制器是否已销毁
checkDestroyed();
// 参数校验
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
// 如果传入的字符串是true,则返回默认的扩展点实例
if ("true".equals(name)) {
return getDefaultExtension();
}
String cacheKey = name;
if (!wrap) {
cacheKey += "_origin";
}
// 获取holder,holder中封装具体的扩展点实例
final Holder<Object> holder = getOrCreateHolder(cacheKey);
// 双重检查、加锁创建holder
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 创建扩展点
instance = createExtension(name, wrap);
// 将扩展点赋值到holder中
holder.set(instance);
}
}
}
return (T) instance;
}
- 传入的name如果是true,则获取默认的扩展点实现,如果没有配则返回null
- 通过name,获取一个holder,holder中存扩展点实例。先通过缓存获取,没有则创建一个,然后添加到缓存中
- 获取扩展点实例,如果为null则通过holder进行加锁,并进行双重检查,然后创建扩展点实例
通过holder对象可以看到,里面只有一个value参数,存的就是扩展点实例。 那为什么要将实例封装在holder中再进行缓存,而不是直接缓存具体实例呢?
从上面获取扩展点源码中就可以看出,首先是从缓存中获取holder,然后通过holder加锁去创建扩展点实例,这一步就是为了细化加锁的粒度,不同的扩展点实例只会有一个锁。
如果我们不适用holder包装一下实例,那缓存中获取的就是null,没办法去处理并发下创建扩展点实例的问题了。
创建扩展点(createExtension)
private T createExtension(String name, boolean wrap) {
// 通过名称,获取扩展接口class
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null || unacceptableExceptions.contains(name)) {
throw findException(name);
}
try {
T instance = (T) extensionInstances.get(clazz);
if (instance == null) {
// 创建实例
extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
instance = (T) extensionInstances.get(clazz);
// 调用实例化前处理器
instance = postProcessBeforeInitialization(instance, name);
// 实例化
injectExtension(instance);
//调用实例化后处理器
instance = postProcessAfterInitialization(instance, name);
}
if (wrap) {
List<Class<?>> wrapperClassesList = new ArrayList<>();
if (cachedWrapperClasses != null) {
wrapperClassesList.addAll(cachedWrapperClasses);
wrapperClassesList.sort(WrapperComparator.COMPARATOR);
Collections.reverse(wrapperClassesList);
}
if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
boolean match =
(wrapper == null) || (
(ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name))
&& !ArrayUtils.contains(wrapper.mismatches(), name)
);
if (match) {
// 获取包装类有type类的构造方法,构建包装类
instance = injectExtension(
(T) wrapperClass.getConstructor(type).newInstance(instance));
instance = postProcessAfterInitialization(instance, name);
}
}
}
}
// Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException(
"Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(),
t);
}
}
- 通过传入的name获取扩展点
- 通过扩展点尝试从缓存中获取具体的实例,没有则创建
- 调用扩展点实例化前的处理器
- 实例化
- 调用扩展点实例化后处理器
- 如果存在包装器,还要创建包装器,并将实例放到包装器中(AOP)
加载文件
private Map<String, Class<?>> loadExtensionClasses() throws InterruptedException {
checkDestroyed();
// 缓存扩展点的默认实例
cacheDefaultExtensionName();
// red=cn.yanggl.RedCar -> <red,RedCar.class>
Map<String, Class<?>> extensionClasses = new HashMap<>();
// 从多个目录下加载文件并解析
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy, type.getName());
// compatible with old ExtensionFactory
if (this.type == ExtensionInjector.class) {
loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());
}
}
return extensionClasses;
}
- 从缓存中获取所有扩展点组成的mapper,如果为空的就加锁然后从文件中加载
- 解析@SPI上标注的默认扩展点,有则进行缓存(@SPI注解上的默认扩展点只能有一个,写了多个就会报错)
- 编列多个目录下进行加载文件并解析,存到extensionClasses中
解析文件
private void loadResource(Map<String, Class<?>> extensionClasses,
ClassLoader classLoader,
java.net.URL resourceURL,
boolean overridden,
String[] includedPackages,
String[] excludedPackages,
String[] onlyExtensionClassLoaderPackages) {
try {
// 获取新文件内容
List<String> newContentList = getResourceContent(resourceURL);
String clazz;
for (String line : newContentList) {
try {
String name = null;
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
clazz = line.substring(i + 1).trim();
} else {
clazz = line;
}
if (StringUtils.isNotEmpty(clazz)
&& !isExcluded(clazz, excludedPackages)
&& isIncluded(clazz, includedPackages)
&& !isExcludedByClassLoader(clazz, classLoader, onlyExtensionClassLoaderPackages)) {
// 加载类,并添加到extensionClasses中
loadClass(classLoader,
extensionClasses,
resourceURL,
Class.forName(clazz, true, classLoader),
name,
overridden);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException(
"Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(),
t);
exceptions.put(line, e);
}
}
} catch (Throwable t) {
logger.error(COMMON_ERROR_LOAD_EXTENSION, "", "",
"Exception occurred when loading extension class (interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL,
t);
}
}
- 首先通过文件路径加载内容
- 便利所有行,并解析行数据,将等号左边作为key,右边的全类路径创建实例作为value存到extensionClasses中
- 判断类是不是有@Adaptive注解
- 判断类是不是wrapper包装类(存在一个参数的构造器并且参数==type)
- 都不是则正常创建(扩展点允许多个别名)
扩展点依赖注入(IOC)
private T injectExtension(T instance) {
if (injector == null) {
return instance;
}
try {
for (Method method : instance.getClass().getMethods()) {
// 不是set方法,跳过
if (!isSetter(method)) {
continue;
}
/**
* Check {@link DisableInject} to see if we need auto-injection for this property
*/
// set方法上有@DisableInject,跳过
if (method.isAnnotationPresent(DisableInject.class)) {
continue;
}
// When spiXXX implements ScopeModelAware, ExtensionAccessorAware,
// the setXXX of ScopeModelAware and ExtensionAccessorAware does not need to be injected
if (method.getDeclaringClass() == ScopeModelAware.class) {
continue;
}
if (instance instanceof ScopeModelAware || instance instanceof ExtensionAccessorAware) {
if (ignoredInjectMethodsDesc.contains(ReflectUtils.getDesc(method))) {
continue;
}
}
// 获取set方法的第一个参数类型
Class<?> pt = method.getParameterTypes()[0];
// 判断是不是基本类型(String、Integer...)
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
// 截取set方法名(setCar -> car)
String property = getSetterProperty(method);
// 获取指定类型和名称的实例
Object object = injector.getInstance(pt, property);
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error(COMMON_ERROR_LOAD_EXTENSION, "", "",
"Failed to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(),
e);
}
}
} catch (Exception e) {
logger.error(COMMON_ERROR_LOAD_EXTENSION, "", "", e.getMessage(), e);
}
return instance;
}
- 便利所有实例的方法,跳过不是set方法、方法上有@DisableInject、实现ScopeModelAware接口的方法
- 获取set方法的第一个参数类型,如果是基本数据类型则跳过
- 截取set方法名(setCar -> car)
- 通过参数类型和名称创建实例
- 通过Spring容器获取
- dubbo自己去生成一个代理类(方法上要加@Adaptive,否则调用方法的时候会报错)
包装器(AOP)
- 获取文件解析时存的所有包装器,降序排序,然后再颠倒成升序
- 便利所有的包装器
- 判断,需要生成实例的条件如下
- 类上没有@Wrapper
- 有@Wrapper注解
- matches为空或者matches包含扩展点名称
- 并且mismatches不包含扩展点名称
- 找包装类上有以扩展类类型为参数的构造方法,通过该构造方法创建包装类
- 传入包装类和扩展类类名,调用初始化后后置处理器