Sofa之ExtensionLoader

    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,

  1. 读取Filter上的注解Extensible
  2. 判断是否是单例,如果不是,那么不进行全局的初始化——不是单例那么每次使用到都要重新创建实例
  3. 在自动加载的情况,读取extension.load.path获取配置文件的目录路径,默认是META-INF/services和META-INF/services/sofa-rpc,遍历这俩个目录,读取该目录下的文件内容
  4. loadFromFile方法中,如果Filter上的Extensible注解有设置file值,那么遍历"META-INF/services+file"和"META-INF/services/sofa-rpc+file"下的文件内容,如果不设置file的值,那么读取Filter的类全名称名称对应的文件
  5. loadFromClassLoader方法中,对每个文件,逐个逐行读取文件内容,如果行是=隔开的,那么=的左边值是别名,右边是实现类。
  6. 之后封装到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值的实现类,则自己被排斥掉,不加入集合。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章