dubbo源碼分析系列——dubbo的SPI機制源碼分析

dubbo的SPI機制

關於dubbo的SPI機制請參閱dubbo開發者文檔 -> http://dubbo.io/Developer+Guide-zh.htm#DeveloperGuide-zh-%E6%89%A9%E5%B1%95%E7%82%B9%E5%8A%A0%E8%BD%BDjava

我只把我本身理解的SPI與你們分享一下:編程

SPI是面向dubbo開發者角色的擴展接口,開發者能夠經過它來擴展dubbo的功能和技術實現。相對而言API是面向dubbo使用者角色編程接口。api

SPI解決的是擴展內容配置和動態加載的問題。在java中解決相同或者相似問題的技術有OSGI,JDK自帶的SPI,以及IOC框架Spring也可以解決相似的問題,各類解決方案各有特色,咱們不展開講。而dubbo的SPI是從JDK標準的SPI(Service Provider Interface)擴展點發現機制增強而來,它作了以下改進:(這些內容引用自dubbo官方開發者手冊)緩存

  • JDK標準的SPI會一次性實例化擴展點全部實現,若是有擴展實現初始化很耗時,但若是沒用上也加載,會很浪費資源。
  • 若是擴展點加載失敗,連擴展點的名稱都拿不到了。好比:JDK標準的ScriptEngine,經過getName();獲取腳本類型的名稱,但若是RubyScriptEngine由於所依賴的jruby.jar不存在,致使RubyScriptEngine類加載失敗,這個失敗緣由被吃掉了,和ruby對應不起來,當用戶執行ruby腳本時,會報不支持ruby,而不是真正失敗的緣由。
  • 增長了對擴展點IoC和AOP的支持,一個擴展點能夠直接setter注入其它擴展點。

那麼dubbo的SPI是如何實現的呢?讓咱們直接看源碼一探究竟,SPI的源碼位於工程dubbo-common的包com.alibaba.dubbo.common.extension下。安全

dubbo的SPI源碼實現分析

ExtensionLoader

該類是dubbo的SPI機制實現的最爲核心的一個類,絕大多數實現邏輯均位於該類中,所以咱們從該類入手研究。爲了簡化,該類是一個將使用api及核心邏輯實現都封裝同一個類中。一般得到一個擴展接口的實例使用以下接口方法得到。ruby

ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(DubboProtocol.NAME);

靜態工廠方法getExtensionLoader

該方法是一個靜態工廠方法,是一個編程api,經過該方法得到一個某個參數制定的擴展類的ExtensionLoader對象。app

private static <T> boolean withExtensionAnnotation(Class<T> type) {
        return type.isAnnotationPresent(SPI.class);
    }
   
    @SuppressWarnings("unchecked")
    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        if(!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        if(!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type + 
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }
        
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

從看源碼咱們得出幾個結論。框架

  1. dubbo的SPI擴展點必須是接口。
  2. dubbo的SPI擴展點接口必須用註解SPI標註。
  3. 某個擴展點的ExtensionLoader是獲取的時候延遲生成,而且會進行緩存。

得到擴展實例方法getExtension

/**
     * 返回指定名字的擴展。若是指定名字的擴展不存在,則拋異常 {@link IllegalStateException}.
     *
     * @param name
     * @return
     */
	@SuppressWarnings("unchecked")
	public T getExtension(String name) {
		if (name == null || name.length() == 0)
		    throw new IllegalArgumentException("Extension name == null");
		if ("true".equals(name)) {
		    return getDefaultExtension();
		}
		Holder<Object> holder = cachedInstances.get(name);
		if (holder == null) {
		    cachedInstances.putIfAbsent(name, new Holder<Object>());
		    holder = cachedInstances.get(name);
		}
		Object instance = holder.get();
		if (instance == null) {
		    synchronized (holder) {
	            instance = holder.get();
	            if (instance == null) {
	                instance = createExtension(name);
	                holder.set(instance);
	            }
	        }
		}
		return (T) instance;
	}
  1. 擴展點名稱不能空,不然拋出異常。
  2. 擴展點名稱傳入true則表示獲取默認擴展點實例。
  3. 擴展點支持緩存,說明擴展點對象在SPI容器中單例的,須要考慮線程安全。
  4. 擴展點對象生成時延遲建立的,實現了對jdk的spi的改進。

獲取默認擴展對象方法getDefaultExtension

/**
	 * 返回缺省的擴展,若是沒有設置則返回<code>null</code>。 
	 */
	public T getDefaultExtension() {
	    getExtensionClasses();
        if(null == cachedDefaultName || cachedDefaultName.length() == 0
                || "true".equals(cachedDefaultName)) {
            return null;
        }
        return getExtension(cachedDefaultName);
	}

得到默認的擴展對象,屬性cachedDefaultName的值並非參數傳遞進來的,它是在方法得到擴展類getExtensionClasses()中賦值的。稍後咱們一塊兒看看該方法的實現。若是沒有加載到默認的擴展點實現,則返回null。ide

建立某種擴展對象方法createExtension函數

private T createExtension(String name) {
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && wrapperClasses.size() > 0) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                    type + ")  could not be instantiated: " + t.getMessage(), t);
        }
    }
  1. 根據擴展名稱得到擴展實現類。先會調用方法getExtensionClasses加載擴展類列表,而後獲取指定名稱對應的擴展類。
  2. 若找不到擴展類則拋出異常。
  3. 從擴展實例緩存中找到該擴展類的實例。若找到則直接返回。
  4. 若未找到緩存對象則產生新對象。調用的方法是clazz.newInstance())。
  5. 給擴展實例執行依賴注入。調用了方法injectExtension,從而實現了對jdk的spi機制的ioc和aop功能擴展。

獲取擴展實現類方法getExtensionClasse()

該方法實現了獲取某個名稱擴展點的實現類,該方法是被幾乎全部的方法調用以前先調用該方法,實現了擴展實現類的加載。

private Class<?> getExtensionClass(String name) {
	    if (type == null)
	        throw new IllegalArgumentException("Extension type == null");
	    if (name == null)
	        throw new IllegalArgumentException("Extension name == null");
	    Class<?> clazz = getExtensionClasses().get(name);
	    if (clazz == null)
	        throw new IllegalStateException("No such extension \"" + name + "\" for " + type.getName() + "!");
	    return clazz;
	}


	private Map<String, Class<?>> getExtensionClasses() {
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
	}

 // 此方法已經getExtensionClasses方法同步過。
    private Map<String, Class<?>> loadExtensionClasses() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if(defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if(value != null && (value = value.trim()).length() > 0) {
                String[] names = NAME_SEPARATOR.split(value);
                if(names.length > 1) {
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }
                if(names.length == 1) cachedDefaultName = names[0];
            }
        }
        
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }


   private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
        String fileName = dir + type.getName();
        try {
            Enumeration<java.net.URL> urls;
            ClassLoader classLoader = findClassLoader();
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL url = urls.nextElement();
                    try {
                        BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
                        try {
                            String line = null;
                            while ((line = reader.readLine()) != null) {
                                final int ci = line.indexOf('#');
                                if (ci >= 0) line = line.substring(0, ci);
                                line = line.trim();
                                if (line.length() > 0) {
                                    try {
                                        String name = null;
                                        int i = line.indexOf('=');
                                        if (i > 0) {
                                            name = line.substring(0, i).trim();
                                            line = line.substring(i + 1).trim();
                                        }
                                        if (line.length() > 0) {
                                            Class<?> clazz = Class.forName(line, true, classLoader);
                                            if (! type.isAssignableFrom(clazz)) {
                                                throw new IllegalStateException("Error when load extension class(interface: " +
                                                        type + ", class line: " + clazz.getName() + "), class " 
                                                        + clazz.getName() + "is not subtype of interface.");
                                            }
                                            if (clazz.isAnnotationPresent(Adaptive.class)) {
                                                if(cachedAdaptiveClass == null) {
                                                    cachedAdaptiveClass = clazz;
                                                } else if (! cachedAdaptiveClass.equals(clazz)) {
                                                    throw new IllegalStateException("More than 1 adaptive class found: "
                                                            + cachedAdaptiveClass.getClass().getName()
                                                            + ", " + clazz.getClass().getName());
                                                }
                                            } else {
                                                try {
                                                    clazz.getConstructor(type);
                                                    Set<Class<?>> wrappers = cachedWrapperClasses;
                                                    if (wrappers == null) {
                                                        cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                                                        wrappers = cachedWrapperClasses;
                                                    }
                                                    wrappers.add(clazz);
                                                } catch (NoSuchMethodException e) {
                                                    clazz.getConstructor();
                                                    if (name == null || name.length() == 0) {
                                                        name = findAnnotationName(clazz);
                                                        if (name == null || name.length() == 0) {
                                                            if (clazz.getSimpleName().length() > type.getSimpleName().length()
                                                                    && clazz.getSimpleName().endsWith(type.getSimpleName())) {
                                                                name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
                                                            } else {
                                                                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
                                                            }
                                                        }
                                                    }
                                                    String[] names = NAME_SEPARATOR.split(name);
                                                    if (names != null && names.length > 0) {
                                                        Activate activate = clazz.getAnnotation(Activate.class);
                                                        if (activate != null) {
                                                            cachedActivates.put(names[0], activate);
                                                        }
                                                        for (String n : names) {
                                                            if (! cachedNames.containsKey(clazz)) {
                                                                cachedNames.put(clazz, n);
                                                            }
                                                            Class<?> c = extensionClasses.get(n);
                                                            if (c == null) {
                                                                extensionClasses.put(n, clazz);
                                                            } else if (c != clazz) {
                                                                throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    } catch (Throwable t) {
                                        IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
                                        exceptions.put(line, e);
                                    }
                                }
                            } // end of while read lines
                        } finally {
                            reader.close();
                        }
                    } catch (Throwable t) {
                        logger.error("Exception when load extension class(interface: " +
                                            type + ", class file: " + url + ") in " + url, t);
                    }
                } // end of while urls
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }
  1. 先從加載的緩衝中獲取擴展點實現類對象。若獲取到則直接返回,不然須要加載。
  2. 緩存有擊穿的風險。即當擴展點無實現類的狀況下會每次都進行擴展點記載,該操做是很是耗時的操做,所以要避免這種狀況。
  3. 加載擴展點實現類。經過調用方法loadExtensionClasses實現。
  4. 擴展點接口上的SPI註解能夠指定默認的擴展點名稱。並且只能設置一個默認擴展點,不容許多個。這個很好理解。代碼能夠看出此時會將SPI註解的value值賦值給屬性cachedDefaultName。
  5. 掃描classpath下的三個目錄加載SPI配置信息。分別類路徑下的:META-INF/services/、META-INF/dubbo/ 和 META-INF/dubbo/internal/,加載順序與該順序正好相反,META-INF/dubbo/internal/ 最先開始加載。
  6. 加載類路徑下全部SPI配置文件。文件命名是:目錄名+{擴展接口的類名},好比擴展接口Protocol則文件名爲:"META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol"。加載這些配置文件中的內容。
  7. 逐行讀取配置文件中的內容。
  8. 配置文件支持'#'後面的均爲注視,能夠忽略。
  9. 配置文件相似properties文件格式。內容格式是: {name}={className}。其中等號左邊是擴展點實現名稱,右邊是擴展點實現的全局類名,須要包含在類路徑中,不然類加載會拋出異常。能夠寫多行,每行表示一個實現類。
  10. 找到類名後會使用當前的classloader加載該類。若加載失敗則會記錄異常。
  11. 檢查該擴展實現類的合法性。該類必須是擴展接口類的實現類。若不是則會拋出異常。
  12. 檢查擴展實現類是否爲Adaptive標註類。若是是註解Adaptive標註的類(在類上註解,方法註解不算),則會將改類賦值給cachedAdaptiveClass類,表示該類爲一個適配器類,而後退出。
  13. 不然嘗試獲取包含參數爲type的構造函數。若是有包含參數爲type的構造函數,則說明該實現類是一個包裝器類,會將cachedWrapperClasses的屬性設置爲該值,而後退出。
  14. 不然繼續得到無參構造函數。嘗試獲取無參數構造函數,若無則會拋出異常。
  15. 檢查擴展類名稱。若是沒有名稱則會嘗試自動生成,即若擴展點定義爲com.alibaba.dubbo.rpc.Protocol,則擴展類名必須爲com.alibaba.dubbo.rpc.XxxProtocol的才能夠生成,自動生成的名稱爲xxx。即將前綴替換爲小寫。不然會拋出異常。
  16. 擴展名稱支持多個。名稱若是是以','隔開,則表示名稱是多個,多個名稱都會被記錄下來,引用相同的擴展實現類。
  17. 若是擴展類有Activate註解,則會將註解實例activate放入集合屬性cachedActivates中。
  18. 將擴展實現的名稱和類分別放入緩存中。以備後續使用。

得到自適應擴展getAdaptiveExtension

ExtensionLoader注入的依賴擴展點是一個Adaptive實例,直到擴展點方法執行時才決定調用是一個擴展點實現。

Dubbo使用URL對象(包含了Key-Value)傳遞配置信息。

擴展點方法調用會有URL參數(或是參數有URL成員)

這樣依賴的擴展點也能夠從URL拿到配置信息,全部的擴展點本身定好配置的Key後,配置信息從URL上從最外層傳入。URL在配置傳遞上便是一條總線

本方法實現了這一特性。

@SuppressWarnings("unchecked")
    public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if(createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            instance = createAdaptiveExtension();
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            }
            else {
                throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }

        return (T) instance;
    }

 @SuppressWarnings("unchecked")
    private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
        }
    }

 private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }


  private Class<?> createAdaptiveExtensionClass() {
        String code = createAdaptiveExtensionClassCode();
        ClassLoader classLoader = findClassLoader();
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }


 private String createAdaptiveExtensionClassCode() {
        StringBuilder codeBuidler = new StringBuilder();
        Method[] methods = type.getMethods();
        boolean hasAdaptiveAnnotation = false;
        for(Method m : methods) {
            if(m.isAnnotationPresent(Adaptive.class)) {
                hasAdaptiveAnnotation = true;
                break;
            }
        }
        // 徹底沒有Adaptive方法,則不須要生成Adaptive類
        if(! hasAdaptiveAnnotation)
            throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");
        
        codeBuidler.append("package " + type.getPackage().getName() + ";");
        codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";");
        codeBuidler.append("\npublic class " + type.getSimpleName() + "$Adpative" + " implements " + type.getCanonicalName() + " {");
        
        for (Method method : methods) {
            Class<?> rt = method.getReturnType();
            Class<?>[] pts = method.getParameterTypes();
            Class<?>[] ets = method.getExceptionTypes();

            Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
            StringBuilder code = new StringBuilder(512);
            if (adaptiveAnnotation == null) {
                code.append("throw new UnsupportedOperationException(\"method ")
                        .append(method.toString()).append(" of interface ")
                        .append(type.getName()).append(" is not adaptive method!\");");
            } else {
                int urlTypeIndex = -1;
                for (int i = 0; i < pts.length; ++i) {
                    if (pts[i].equals(URL.class)) {
                        urlTypeIndex = i;
                        break;
                    }
                }
                // 有類型爲URL的參數
                if (urlTypeIndex != -1) {
                    // Null Point check
                    String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");",
                                    urlTypeIndex);
                    code.append(s);
                    
                    s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex); 
                    code.append(s);
                }
                // 參數沒有URL類型
                else {
                    String attribMethod = null;
                    
                    // 找到參數的URL屬性
                    LBL_PTS:
                    for (int i = 0; i < pts.length; ++i) {
                        Method[] ms = pts[i].getMethods();
                        for (Method m : ms) {
                            String name = m.getName();
                            if ((name.startsWith("get") || name.length() > 3)
                                    && Modifier.isPublic(m.getModifiers())
                                    && !Modifier.isStatic(m.getModifiers())
                                    && m.getParameterTypes().length == 0
                                    && m.getReturnType() == URL.class) {
                                urlTypeIndex = i;
                                attribMethod = name;
                                break LBL_PTS;
                            }
                        }
                    }
                    if(attribMethod == null) {
                        throw new IllegalStateException("fail to create adative class for interface " + type.getName()
                        		+ ": not found url parameter or url attribute in parameters of method " + method.getName());
                    }
                    
                    // Null point check
                    String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
                                    urlTypeIndex, pts[urlTypeIndex].getName());
                    code.append(s);
                    s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
                                    urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
                    code.append(s);

                    s = String.format("%s url = arg%d.%s();",URL.class.getName(), urlTypeIndex, attribMethod); 
                    code.append(s);
                }
                
                String[] value = adaptiveAnnotation.value();
                // 沒有設置Key,則使用「擴展點接口名的點分隔 做爲Key
                if(value.length == 0) {
                    char[] charArray = type.getSimpleName().toCharArray();
                    StringBuilder sb = new StringBuilder(128);
                    for (int i = 0; i < charArray.length; i++) {
                        if(Character.isUpperCase(charArray[i])) {
                            if(i != 0) {
                                sb.append(".");
                            }
                            sb.append(Character.toLowerCase(charArray[i]));
                        }
                        else {
                            sb.append(charArray[i]);
                        }
                    }
                    value = new String[] {sb.toString()};
                }
                
                boolean hasInvocation = false;
                for (int i = 0; i < pts.length; ++i) {
                    if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
                        // Null Point check
                        String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
                        code.append(s);
                        s = String.format("\nString methodName = arg%d.getMethodName();", i); 
                        code.append(s);
                        hasInvocation = true;
                        break;
                    }
                }
                
                String defaultExtName = cachedDefaultName;
                String getNameCode = null;
                for (int i = value.length - 1; i >= 0; --i) {
                    if(i == value.length - 1) {
                        if(null != defaultExtName) {
                            if(!"protocol".equals(value[i]))
                                if (hasInvocation) 
                                    getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                                else
                                    getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
                            else
                                getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
                        }
                        else {
                            if(!"protocol".equals(value[i]))
                                if (hasInvocation) 
                                    getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                                else
                                    getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
                            else
                                getNameCode = "url.getProtocol()";
                        }
                    }
                    else {
                        if(!"protocol".equals(value[i]))
                            if (hasInvocation) 
                                getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                            else
                                getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
                        else
                            getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
                    }
                }
                code.append("\nString extName = ").append(getNameCode).append(";");
                // check extName == null?
                String s = String.format("\nif(extName == null) " +
                		"throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");",
                        type.getName(), Arrays.toString(value));
                code.append(s);
                
                s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
                        type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
                code.append(s);
                
                // return statement
                if (!rt.equals(void.class)) {
                    code.append("\nreturn ");
                }

                s = String.format("extension.%s(", method.getName());
                code.append(s);
                for (int i = 0; i < pts.length; i++) {
                    if (i != 0)
                        code.append(", ");
                    code.append("arg").append(i);
                }
                code.append(");");
            }
            
            codeBuidler.append("\npublic " + rt.getCanonicalName() + " " + method.getName() + "(");
            for (int i = 0; i < pts.length; i ++) {
                if (i > 0) {
                    codeBuidler.append(", ");
                }
                codeBuidler.append(pts[i].getCanonicalName());
                codeBuidler.append(" ");
                codeBuidler.append("arg" + i);
            }
            codeBuidler.append(")");
            if (ets.length > 0) {
                codeBuidler.append(" throws ");
                for (int i = 0; i < ets.length; i ++) {
                    if (i > 0) {
                        codeBuidler.append(", ");
                    }
                    codeBuidler.append(ets[i].getCanonicalName());
                }
            }
            codeBuidler.append(" {");
            codeBuidler.append(code.toString());
            codeBuidler.append("\n}");
        }
        codeBuidler.append("\n}");
        if (logger.isDebugEnabled()) {
            logger.debug(codeBuidler.toString());
        }
        return codeBuidler.toString();
    }
  1. 先從緩存中得到擴展點適配實例。若是有則直接得到,不然須要建立。
  2. 如有類經過屬性cachedAdaptiveClass的類建立。不然須要獲取類。
  3. 若是獲取類失敗,則須要調用方法createAdaptiveExtensionClass動態建立適配器類。
  4. 若是擴展點類type沒有方法被註解Adaptive註釋過,則無需建立適配器類,則拋出異常。
  5. 動態生成一個適配器實現類。生成的類名是: type.getSimpleName() + "$Adpative"。
  6. 生成的類要實現每個type中包含Adaptive註釋過方法。若沒有則該方法被拋出不支持的異常。
  7. 實現包含Adaptive註釋過方法。先經過type參數類型爲URL或者getXXX返回值爲URL的得到url對象值。而且會檢查url值是否爲空,若是爲空則會拋出異常。
  8. 若方法上的Adaptive註解的value是空,則使用「擴展點接口名的點分隔" 做爲Key。
  9. 以value做爲key,從url對象中得到值,而後將得到的值調用方法getExtension得到擴展點實現對象。這樣就實現了動態注入擴展實現。

相關內容的進一步分析請參考另一篇文章:http://my.oschina.net/ywbrj042/blog/688042

得到自動激活擴展列表方法getActivateExtension

對於集合類擴展點,好比:Filter, InvokerListener, ExportListener, TelnetHandler, StatusChecker等,
能夠同時加載多個實現,此時,能夠用自動激活來簡化配置。本方法實現了這一特性。

/**
     * This is equivalent to <pre>
     *     getActivateExtension(url, key, null);
     * </pre>
     *
     * @param url url
     * @param key url parameter key which used to get extension point names
     * @return extension list which are activated.
     * @see #getActivateExtension(com.alibaba.dubbo.common.URL, String, String)
     */
    public List<T> getActivateExtension(URL url, String key) {
        return getActivateExtension(url, key, null);
    }

    /**
     * This is equivalent to <pre>
     *     getActivateExtension(url, values, null);
     * </pre>
     *
     * @see #getActivateExtension(com.alibaba.dubbo.common.URL, String[], String)
     * @param url url
     * @param values extension point names
     * @return extension list which are activated
     */
    public List<T> getActivateExtension(URL url, String[] values) {
        return getActivateExtension(url, values, null);
    }

    /**
     * This is equivalent to <pre>
     *     getActivateExtension(url, url.getParameter(key).split(","), null);
     * </pre>
     *
     * @see #getActivateExtension(com.alibaba.dubbo.common.URL, String[], String)
     * @param url url
     * @param key url parameter key which used to get extension point names
     * @param group group
     * @return extension list which are activated.
     */
    public List<T> getActivateExtension(URL url, String key, String group) {
        String value = url.getParameter(key);
        return getActivateExtension(url, value == null || value.length() == 0 ? null : Constants.COMMA_SPLIT_PATTERN.split(value), group);
    }

    /**
     * Get activate extensions.
     *
     * @see com.alibaba.dubbo.common.extension.Activate
     * @param url url
     * @param values extension point names
     * @param group group
     * @return extension list which are activated
     */
    public List<T> getActivateExtension(URL url, String[] values, String group) {
        List<T> exts = new ArrayList<T>();
        List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
        if (! names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
            getExtensionClasses();
            for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
                String name = entry.getKey();
                Activate activate = entry.getValue();
                if (isMatchGroup(group, activate.group())) {
                    T ext = getExtension(name);
                    if (! names.contains(name)
                            && ! names.contains(Constants.REMOVE_VALUE_PREFIX + name) 
                            && isActive(activate, url)) {
                        exts.add(ext);
                    }
                }
            }
            Collections.sort(exts, ActivateComparator.COMPARATOR);
        }
        List<T> usrs = new ArrayList<T>();
        for (int i = 0; i < names.size(); i ++) {
        	String name = names.get(i);
            if (! name.startsWith(Constants.REMOVE_VALUE_PREFIX)
            		&& ! names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {
            	if (Constants.DEFAULT_KEY.equals(name)) {
            		if (usrs.size() > 0) {
	            		exts.addAll(0, usrs);
	            		usrs.clear();
            		}
            	} else {
	            	T ext = getExtension(name);
	            	usrs.add(ext);
            	}
            }
        }
        if (usrs.size() > 0) {
        	exts.addAll(usrs);
        }
        return exts;
    }

 private boolean isMatchGroup(String group, String[] groups) {
        if (group == null || group.length() == 0) {
            return true;
        }
        if (groups != null && groups.length > 0) {
            for (String g : groups) {
                if (group.equals(g)) {
                    return true;
                }
            }
        }
        return false;
    }
    
    private boolean isActive(Activate activate, URL url) {
        String[] keys = activate.value();
        if (keys == null || keys.length == 0) {
            return true;
        }
        for (String key : keys) {
            for (Map.Entry<String, String> entry : url.getParameters().entrySet()) {
                String k = entry.getKey();
                String v = entry.getValue();
                if ((k.equals(key) || k.endsWith("." + key))
                        && ConfigUtils.isNotEmpty(v)) {
                    return true;
                }
            }
        }
        return false;
    }

源碼解析:

  1. 有多個重載方法能夠得到自動激活的擴展實例列表。
  2. 若是values(即擴展點名稱)列表中不包含默認值「-default」則開始遍歷map對象cachedActivates中的全部activate。
  3. 若擴展點被Activate註解標準,則檢查activate中的group是否匹配,
相關文章
相關標籤/搜索