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值的實現類,則自己被排斥掉,不加入集合。

 

 

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