Sofa-rpc版本5.6.1
ExtensionLoader用于加载Sofa的组件,类似JDK的SPI,Dubbo和其它框架中也有很多这种ExtensionLoader。
从实际的运行情况来看,会从META-INF/services和META-INF/services/sofa-rpc目录下读取文件,然后读取文件内容,来加载自定义的类。
Extensible
extensible注解表示这个类/接口是可拓展的,如果我们指定file的指,比如file的值为hello-world,那表示加载默认目录下的名称为hello-world的文件,读取里面的配置,如果不设置那么按默认目录读取类全名的文件,下面会说到。
List-1
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
public @interface Extensible {
/**
* 指定自定义扩展文件名称,默认就是全类名
*
* @return 自定义扩展文件名称
*/
String file() default "";
/**
* 扩展类是否使用单例,默认使用
*
* @return 是否使用单例
*/
boolean singleton() default true;
/**
* 扩展类是否需要编码,默认不需要
*
* @return 是否需要编码
*/
boolean coded() default false;
}
ExtensionLoader
以加载Sofa filter为例,如下List-2,
- 读取Filter上的注解Extensible
- 判断是否是单例,如果不是,那么不进行全局的初始化——不是单例那么每次使用到都要重新创建实例
- 在自动加载的情况,读取extension.load.path获取配置文件的目录路径,默认是META-INF/services和META-INF/services/sofa-rpc,遍历这俩个目录,读取该目录下的文件内容
- loadFromFile方法中,如果Filter上的Extensible注解有设置file值,那么遍历"META-INF/services+file"和"META-INF/services/sofa-rpc+file"下的文件内容,如果不设置file的值,那么读取Filter的类全名称名称对应的文件
- loadFromClassLoader方法中,对每个文件,逐个逐行读取文件内容,如果行是=隔开的,那么=的左边值是别名,右边是实现类。
- 之后封装到ExtensionClass中,之后如果ExtensionLoaderListener不为空,那么调用onLoad方法分发加载事件。
List-2
protected ExtensionLoader(Class<T> interfaceClass, boolean autoLoad, ExtensionLoaderListener<T> listener) {
...
}
this.interfaceClass = interfaceClass;
this.interfaceName = ClassTypeUtils.getTypeStr(interfaceClass);
this.listeners = new ArrayList<>();
if (listener != null) {
listeners.add(listener);
}
Extensible extensible = interfaceClass.getAnnotation(Extensible.class);
if (extensible == null) {
throw new IllegalArgumentException(
"Error when load extensible interface " + interfaceName + ", must add annotation @Extensible.");
} else {
this.extensible = extensible;
}
this.factory = extensible.singleton() ? new ConcurrentHashMap<String, T>() : null;
this.all = new ConcurrentHashMap<String, ExtensionClass<T>>();
if (autoLoad) {
List<String> paths = RpcConfigs.getListValue(RpcOptions.EXTENSION_LOAD_PATH);
for (String path : paths) {
loadFromFile(path);
}
}
}
...
protected synchronized void loadFromFile(String path) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Loading extension of extensible {} from path: {}", interfaceName, path);
}
// 默认如果不指定文件名字,就是接口名
String file = StringUtils.isBlank(extensible.file()) ? interfaceName : extensible.file().trim();
String fullFileName = path + file;
try {
ClassLoader classLoader = ClassLoaderUtils.getClassLoader(getClass());
loadFromClassLoader(classLoader, fullFileName);
} catch (Throwable t) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Failed to load extension of extensible " + interfaceName + " from path:" + fullFileName,
t);
}
}
}
...
protected void loadFromClassLoader(ClassLoader classLoader, String fullFileName) throws Throwable {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources(fullFileName)
: ClassLoader.getSystemResources(fullFileName);
// 可能存在多个文件。
if (urls != null) {
while (urls.hasMoreElements()) {
// 读取一个文件
URL url = urls.nextElement();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Loading extension of extensible {} from classloader: {} and file: {}",
interfaceName, classLoader, url);
}
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"));
String line;
while ((line = reader.readLine()) != null) {
readLine(url, line);
}
...
上面的步骤6中,还涉及一些细节,重名情况下覆盖和排斥其它拓展。
覆盖:如果集合中不存在重名的别名,则不会存在覆盖的情况。如果当前扩展可以覆盖其它同名扩展,如果当前扩展order小于旧扩展则不会将当前扩展加入集合;如果当前扩展order大于旧扩展,则将当前扩展覆盖旧扩展。如果当前扩展没设置覆盖其它同名扩展,但是旧扩展设置了,且旧order大于等于当前扩展,则不将当前扩展加入集合,如果旧扩展没设置可覆盖,那么会抛出异常。
排斥:上面的步骤6中,新扫描到的Filter实现类,在配置了排斥点rejection点的情况下,遍历之前已经在加载了的Filter实现类,对比Order,如果Order比集合中的大,移除集合中别名是rejection值的实现类,并将自己添加进去;如果Order比集合中的小,那么如果集合中已经存在别名是rejection值的实现类,则自己被排斥掉,不加入集合。