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