前一篇 《SPI框架實現之旅二:總體設計》中,介紹了幾個定義的接口,註解;敘述了實現流程;並簡單的介紹了
SpiLoader
中的部分實現; 本篇則主要介紹SpiLoader
類的實現java
類圖結構以下:git
spiImpl選擇的核心類,包括初始化選擇器,初始化spiImpl實現列表,解析spiImpl的選擇條件,返回具體的實現類等G緩存
SpiLoader
是一個泛型對象,每一個SPI接口,對應一個SpiLoader<T>
對象,咱們提供了一個靜態方法來獲取這個對象安全
優先從緩存中獲取, 若是緩存沒有,則新建一個;緩存中有, 則直接返回app
/** * spiLoader緩存, 其中key爲 spi接口, value爲對應的Loader對象 */ private static final ConcurrentMap<Class<?>, SpiLoader<?>> loaderCache = new ConcurrentHashMap<>(); @SuppressWarnings("unchecked") public static <T> SpiLoader<T> load(Class<T> type) { if (null == type) { throw new IllegalArgumentException("common cannot be null..."); } if (!type.isInterface()) { throw new IllegalArgumentException("common class:" + type + " must be interface!"); } if (!withSpiAnnotation(type)) { throw new IllegalArgumentException("common class:" + type + " must have the annotation of @Spi"); } SpiLoader<T> spiLoader = (SpiLoader<T>) loaderCache.get(type); if (spiLoader == null) { loaderCache.putIfAbsent(type, new SpiLoader<>(type)); spiLoader = (SpiLoader<T>) loaderCache.get(type); } return spiLoader; }
SpiLoader
對象建立對象,主要會初始化選擇器框架
private SpiLoader(Class<T> type) { // 初始化默認的選擇器, 爲保留項目, 必然會提供的服務 selectorInstanceCacheMap.putIfAbsent(DefaultSelector.class, DEFAULT_SELECTOR); this.spiInterfaceType = type; initSelector(); } private void initSelector() { Spi ano = spiInterfaceType.getAnnotation(Spi.class); if (ano == null) { currentSelector = initSelector(DefaultSelector.class); } else { currentSelector = initSelector(ano.selector()); } Method[] methods = this.spiInterfaceType.getMethods(); currentMethodSelector = new ConcurrentHashMap<>(); SelectorWrapper temp; for (Method method : methods) { if (!method.isAnnotationPresent(SpiAdaptive.class)) { continue; } temp = initSelector(method.getAnnotation(SpiAdaptive.class).selector()); if (temp == null) { continue; } currentMethodSelector.put(method.getName(), temp); } } private SelectorWrapper initSelector(Class<? extends ISelector> clz) { // 優先從選擇器緩存中獲取類型對應的選擇器 if (selectorInstanceCacheMap.containsKey(clz)) { return selectorInstanceCacheMap.get(clz); } try { ISelector selector = clz.newInstance(); Class paramClz = null; Type[] types = clz.getGenericInterfaces(); for (Type t : types) { if (t instanceof ParameterizedType) { paramClz = (Class) ((ParameterizedType) t).getActualTypeArguments()[0]; break; } } Assert.check(paramClz != null); SelectorWrapper wrapper = new SelectorWrapper(selector, paramClz); selectorInstanceCacheMap.putIfAbsent(clz, wrapper); return wrapper; } catch (Exception e) { throw new IllegalArgumentException("illegal selector defined! yous:" + clz); } }
selectorInstanceCacheMap
ParamsSelector
內部緩存了spi實現的列表,若是全局公用的話,就會混掉,致使這個列表中就出現非這個spi接口的實現類currentSelector
: 類選擇器, 解析 @Spi
註解獲取,適用於靜態選擇 + 動態選擇兩種使用方式currentMethodSelector
: 方法選擇器,解析 @SpiAdaptive
註解獲取, 僅適用於動態選擇SPI實現的方式靜態使用方式,表示根據傳入的條件,選擇一個知足條件的實現返回函數
/** * 根據傳入條件, 選擇具體的spi實現類 * <p/> * 這裏要求conf的類型和選擇器的參數類型匹配, 不然會嘗試使用默認的選擇器補救, 若補救失敗, 則拋異常 * * @param conf * @return * @throws NoSpiMatchException * @throws IllegalArgumentException */ @SuppressWarnings("unchecked") public T getService(Object conf) throws NoSpiMatchException { if (spiImplClassCacheMap == null || spiImplClassCacheMap.size() == 0) { loadSpiService(); } if (!currentSelector.getConditionType().isAssignableFrom(conf.getClass())) { /** * 參數類型不匹配時, 判斷是否能夠根據默認的選擇器來獲取 */ if (conf instanceof String) { return (T) DEFAULT_SELECTOR.getSelector().selector(spiImplClassCacheMap, conf); } /** * 參數類型徹底不匹配, 則拋參數異常 */ throw new IllegalArgumentException("conf spiInterfaceType should be sub class of [" + currentSelector.getConditionType() + "] but yours:" + conf.getClass()); } return (T) currentSelector.getSelector().selector(spiImplClassCacheMap, conf); }
spiImplClassCacheMap
性能
spi實現的緩存映射表,優先判斷緩存映射表是否存在,不存在時須要初始化;存在時,則進入校驗邏輯測試
校驗ui
校驗傳入的參數,是否匹配當前的選擇器參數類型,爲了保證選擇器能夠正常運行
當不匹配時,會有一個兼容邏輯,判斷傳參類型是否爲String, 是則採用默認的選擇器,根據name來選擇spi實現 (這種實現可能形成選擇的實現不是預期的)
靜態使用方式,使用類定義選擇器 : currentSelector
靜態使用的方式,目標就是事前就確認使用這個實現了,不會出現變更了; 至關於一次確認,全部的調用都是確認的
靜態使用,方法註解的選擇器無效。這個咱們從逆向的思路進行解釋
IPrint 是一個Spi接口, 有兩個實現 FilePrint, ConsolePrint 假設 `currentSelector=DefaultSelector`, 方法 methodA 上定義的是 ParamsSelector 時 靜態使用方式,獲取一個spi實現,但願在全部的spi接口使用處,都輸出到文件,用戶根據 `FilePrint` 選擇 FilePrint 這個類來執行具體的輸出邏輯, 若是在調用 methodA 方法執行時, 假設根據 ParamsSelector 判斷, ConsolePrint 才知足這兒條件,這是至關於在具體實現時,換成了另外一個 ConsolePrint, 這下子就與咱們的初衷背離了(若是目標是想實現這個場景,顯然動態適配的方式纔是正確的使用姿式)
loadService 的邏輯後面詳細說明
動態使用區別於靜態的直接肯定實現類, 經過
getService
獲取的並非某個特定對的實現類,而是一個動態生成的代理,每次具體執行以前,會去判斷一下,應該選擇哪個實現來執行
能夠考慮下,咱們的目標是在執行方法以前,須要判斷一下哪一個實現類知足要求,選擇這個實現類來執行這個方法,那麼咱們能夠怎麼去作?
考慮到切面的方式,若是有一種手段,在方法執行以前,織入一段業務邏輯,就能夠達到上面的目的
最開始雖然是怎麼想的,可是有點尷尬的是,不知道怎麼去實現;所以換了一個思路,我本身新生成一個接口的實現類,在這個實現類裏面作選擇邏輯,而後把這個實現類對象返回
和靜態實現的邏輯差很少,通常流程以下:
@SuppressWarnings("unchecked") public T getService(Object conf, String methodName) throws NoSpiMatchException { if (spiImplClassCacheMap == null || spiImplClassCacheMap.size() == 0) { loadSpiService(); } // 首先獲取對應的selector SelectorWrapper selector = currentMethodSelector.get(methodName); if (selector == null) { // 自適應方法上未定義選擇器, 則默認繼承類的 selector = currentSelector; currentMethodSelector.putIfAbsent(methodName, selector); } if (!selector.getConditionType().isAssignableFrom(conf.getClass())) { // 選擇器類型校驗 if (!(conf instanceof String)) { throw new IllegalArgumentException("conf spiInterfaceType should be sub class of [" + currentSelector.getConditionType() + "] but yours:" + conf.getClass()); } selector = DEFAULT_SELECTOR; } if (spiImplMethodCacheMap.size() == 0) { return (T) selector.getSelector().selector(spiImplClassCacheMap, conf); } try { // 採用默認的選擇器,根據指定name 進行查詢時, 須要兼容一下, 由於method對應的緩存key爲 SpiImpName_methodName if (DEFAULT_SELECTOR.equals(selector)) { if (spiImplMethodCacheMap.containsKey(conf)) { return (T) selector.getSelector().selector(spiImplMethodCacheMap, conf); } if (spiImplClassCacheMap.containsKey(conf)) { return (T) selector.getSelector().selector(spiImplClassCacheMap, conf); } return (T) selector.getSelector().selector(spiImplMethodCacheMap, conf + "_" + methodName); } else { return (T) selector.getSelector().selector(spiImplMethodCacheMap, conf); } } catch (Exception e) { return (T) selector.getSelector().selector(spiImplClassCacheMap, conf); } }
這個方法一般是由框架生成的代理實現類來調用(後面會說明動態生成代理類的邏輯)
區別與靜態使用方式, 優先根據方法名,查找對應的選擇器;當未定義時,使用類選擇器
默認選擇器,根據name來查詢實現時,傳入的參數特殊處理下,主要是由於 spiImplMethodCacheMap
中key的生成,有一個小轉換
若實現類上沒有 @SpiConf註解,或者 @SpiConf的註解沒有定義 name 屬性,則類的惟一標識name爲:簡單類名; 不然爲指定的name屬性 若方法上顯示使用 @SpiConf 指定了name屬性,則key的生成規則爲: 方法註解上指定的name; 若是沒有 @SpiConf註解,或其中沒有指定name屬性,則key生成規則: 類name屬性 + 下劃線 + 方法名
這一點單獨看可能不太好理解,所以能夠和下面的spi實現類映射關係的初始化結合起來
動態生成代理類的邏輯,放在最後進行說明
爲了不每次選擇具體的實現類時,都去加載一遍,耗時耗力好性能,所以加一個緩存是頗有必要的,這裏主要說下這個實現邏輯,以及爲啥這麼幹
使用了兩個Map:
spiImplClassCacheMap
@SpiConf
註解中定義的name; 或者spi實現類的簡單類名spiImplMethodCacheMap
@SpiConf
註解中定義的name; 或者是 實現類的 name + "_" + 方法名/** * name : spiImpl 的映射表 */ private Map<String, SpiImplWrapper<T>> spiImplClassCacheMap; /** * 自適應時, 根據方法選擇實現; name : spiImpl 的映射表 */ private Map<String, SpiImplWrapper<T>> spiImplMethodCacheMap;
private void loadSpiService() { List<SpiImplWrapper<T>> spiServiceList = new ArrayList<>(); List<SpiImplWrapper<T>> spiServiceMethodList = new ArrayList<>(); ServiceLoader<T> serviceLoader = ServiceLoader.load(spiInterfaceType); SpiConf spiConf; String implName; int implOrder; for (T t : serviceLoader) { spiConf = t.getClass().getAnnotation(SpiConf.class); Map<String, String> map; if (spiConf == null) { implName = t.getClass().getSimpleName(); implOrder = SpiImplWrapper.DEFAULT_ORDER; // 參數選擇器時, 要求spi實現類必須有 @SpiConf 註解, 不然選擇器沒法獲取校驗條件參數 if (currentSelector.getSelector() instanceof ParamsSelector) { throw new IllegalStateException("spiImpl must contain annotation @SpiConf!"); } map = Collections.emptyMap(); } else { implName = spiConf.name(); if (StringUtils.isBlank(implName)) { implName = t.getClass().getSimpleName(); } implOrder = spiConf.order() < 0 ? SpiImplWrapper.DEFAULT_ORDER : spiConf.order(); map = parseParms(spiConf.params()); } // 添加一個類級別的封裝類 spiServiceList.add(new SpiImplWrapper<>(t, implOrder, implName, map)); // todo 改爲 getMethods(), 可是過濾掉 Object類中的基礎方法 Method[] methods = t.getClass().getDeclaredMethods(); String methodImplName; int methodImplOrder; Map<String, String> methodParams; for (Method method : methods) { spiConf = method.getAnnotation(SpiConf.class); if (spiConf == null) { continue; } // 方法上有自定義註解, 且定義的name與類實現名不一樣, 則直接採用 // 不然採用 ServiceName_MethodName 方式定義 if (StringUtils.isBlank(spiConf.name()) || implName.equals(spiConf.name())) { methodImplName = implName + "_" + method.getName(); } else { methodImplName = spiConf.name(); } // 優先級, 以最小的爲準 (即一個類上的優先級很低, 也能夠定義優先級高的方法) // 方法註解未定義順序時, 繼承類上的順序 methodImplOrder = Math.min(implOrder, spiConf.order() < 0 ? implOrder : spiConf.order()); // 自適應方法的參數限制, 要求繼承類上的參數 methodParams = parseParms(spiConf.params()); if (map.size() > 0) { // 方法的參數限定會繼承類上的參數限定 if (methodParams.size() == 0) { methodParams = map; } else { methodParams.putAll(map); } } spiServiceMethodList.add(new SpiImplWrapper<>(t, methodImplOrder, methodImplName, methodParams)); } } if (spiServiceList.size() == 0) { throw new IllegalStateException("no spiImpl implements spi: " + spiInterfaceType); } this.spiImplClassCacheMap = initSpiImplMap(spiServiceList); this.spiImplMethodCacheMap = initSpiImplMap(spiServiceMethodList); } private Map<String, SpiImplWrapper<T>> initSpiImplMap(List<SpiImplWrapper<T>> list) { // 映射爲map, 限定不能重名 Map<String, SpiImplWrapper<T>> tempMap = new ConcurrentHashMap<>(); for (SpiImplWrapper<T> wrapper : list) { if (tempMap.containsKey(wrapper.getName())) { throw new IllegalArgumentException("duplicate spiImpl name " + wrapper.getName()); } tempMap.put(wrapper.getName(), wrapper); } return tempMap; }
上面的邏輯能夠分爲兩塊,一塊是上半邊的初始化,獲取spiImplClassCacheMap
; 下一塊則是掃描實現類的全部方法,將方法上標有@SpiConf
註解的撈出來,用於初始化 spiImplMethodCacheMap
緩存結構中value爲 SpiImplWrapper
緩存value並非簡單的實現類,封裝類的定義以下,將條件和排序也同時封裝進去了
private T spiImpl; private int order; /** * spiImpl 的標識name, 要求惟一 * <p/> * {@link com.hust.hui.quicksilver.spi.selector.DefaultSelector 選擇具體的SpiImpl 時使用} */ private String name; /** * 參數校驗規則 * <p/> * {@link com.hust.hui.quicksilver.spi.selector.ParamsSelector} 選擇具體的SpiImpl 時使用 * 要求每一個實現類都有註解 {@link SpiConf} */ private Map<String, String> paramCondition;
name 的定義,類與方法兩個緯度的緩存中,定義規則不一樣,具體能夠看《緩存結構》這裏的說明
採用 ParamsSelector
時, 要求 @SpiConf
註解必須存在
注意掃描全部方法對應的註解, spi實現類,若是存在繼承則會出現問題
// todo 改爲 getMethods(), 可是過濾掉 Object類中的基礎方法 Method[] methods = t.getClass().getDeclaredMethods();
上面在談論動態使用的時候,採用的方案是,生成一個代理類,實現spi接口, 在具體的實現邏輯中,使用選擇器來獲取知足條件的實現類,而後執行相應的方法
採用倒推方式,先給出一個實際的代理類以下,具體的實現中其實只有兩行代碼
SpiLoader.getService(conf, methodName
)package com.hust.hui.quicksilver.spi.test.print; import com.hust.hui.quicksilver.spi.SpiLoader; public class IPrint$Proxy implements com.hust.hui.quicksilver.spi.test.print.IPrint { public void print(java.lang.String arg0) { try { com.hust.hui.quicksilver.spi.test.print.IPrint spiImpl = SpiLoader.load(com.hust.hui.quicksilver.spi.test.print.IPrint.class).getService(arg0, "print"); spiImpl.print(arg0); } catch (com.hust.hui.quicksilver.spi.exception.NoSpiMatchException e) { throw new java.lang.RuntimeException(e); } } public void adaptivePrint(java.lang.String arg0, java.lang.String arg1) { try { com.hust.hui.quicksilver.spi.test.print.IPrint spiImpl = SpiLoader.load(com.hust.hui.quicksilver.spi.test.print.IPrint.class).getService(arg0, "adaptivePrint"); spiImpl.adaptivePrint(arg0, arg1); } catch (com.hust.hui.quicksilver.spi.exception.NoSpiMatchException e) { throw new java.lang.RuntimeException(e); } } }
上面給出了一個代理類的演示,那麼剩下兩個問題,一個是如何生成代理類; 一個是如何運行代理類(上面是java代碼,咱們知道運行得是字節碼才行)
對着上面的實現,反推代碼生成,其實比較簡單了,無非就是生成一大串的String罷了,這裏真沒什麼特殊的,貼下實現,邏輯省略
/** * 構建SPI接口的實現代理類, 在執行動態適配的方法時, 調用SpiLoader的 spiImpl選擇器, 選擇具體的實現類執行 * * @return */ public static String buildTempImpl(Class type) { StringBuilder codeBuilder = new StringBuilder(); codeBuilder.append("package ").append(type.getPackage().getName()).append(";"); codeBuilder.append("\nimport ").append(SpiLoader.class.getName()).append(";"); codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Proxy implements ").append(type.getCanonicalName()).append(" {\n"); Method[] methods = type.getMethods(); for (Method method : methods) { Class<?> returnType = method.getReturnType(); //函數返回值 Class<?>[] parameterTypes = method.getParameterTypes();//函數參數列表 Class<?>[] exceptionTypes = method.getExceptionTypes();//函數異常列表 // build method code StringBuilder code = new StringBuilder(512); if (parameterTypes.length < 0) { //檢查該函數參數列表中,第一個參數做爲選擇器參數 code.append("throw new IllegalArgumentException(\"there should be one argument for selector to choose spiImpl\")"); } else { // 沒有 SpiAdaptive註解的, 採用默認的註解方式 code.append("try{\n"); code.append(type.getName()).append(" spiImpl=") .append("SpiLoader.load(") .append(type.getName()).append(".class") .append(").getService(arg0,\"") .append(method.getName()) .append("\");"); if (!"void".equals(returnType.getName())) { code.append("return "); } code.append("spiImpl.").append(method.getName()).append("(arg0"); for (int i = 1; i < parameterTypes.length; i++) { code.append(",").append("arg").append(i); } code.append(");"); code.append("\n} catch(com.hust.hui.quicksilver.spi.exception.NoSpiMatchException e){\nthrow new java.lang.RuntimeException(e);\n}"); } // build method signature codeBuilder.append("\npublic ").append(returnType.getName()).append(" ").append(method.getName()) .append("(").append(parameterTypes[0].getName()).append(" arg0"); for (int i = 1; i < parameterTypes.length; i++) { codeBuilder.append(", ").append(parameterTypes[i].getName()).append(" arg").append(i); } codeBuilder.append(") "); if (exceptionTypes.length > 0) { codeBuilder.append("throw ").append(exceptionTypes[0].getName()); for (int i = 1; i < exceptionTypes.length; i++) { codeBuilder.append(", ").append(exceptionTypes[i].getName()); } } codeBuilder.append("{\n"); codeBuilder.append(code.toString()).append("\n}"); } codeBuilder.append("\n}"); return codeBuilder.toString(); }
動態編譯,最開始想的是利用jdk的動態編譯方式,試來試去沒搞成功,而後選擇了一個折中的方案,把代理類當作是groovy代碼,利用 GroovyEngine 來實現動態運行, 這一塊的邏輯也超級簡單,下面的短短几行代碼便可; 後面有空單獨研究下java的動態編譯
@SuppressWarnings("unchecked") public static <T> T compile(String code, Class<T> interfaceType, ClassLoader classLoader) throws SpiProxyCompileException { GroovyClassLoader loader = new GroovyClassLoader(classLoader); Class clz = loader.parseClass(code); if (!interfaceType.isAssignableFrom(clz)) { throw new IllegalStateException("illegal proxy type!"); } try { return (T) clz.newInstance(); } catch (Exception e) { throw new SpiProxyCompileException("init spiProxy error! msg: " + e.getMessage()); } }
至此,核心的東西基本上都過了一遍,主要的設計思路,實現邏輯,執行流程都說完了,目前的實現中,仍有一些缺陷,歡迎提出問題和建議,無比感激
博客系列連接:
源碼地址:
https://git.oschina.net/liuyueyi/quicksilver/tree/master/silver-spi