Dubbo實現原理之基於SPI思想實現Dubbo內核

  dubbo中SPI接口的定義以下:java

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {

    /**
     * 缺省擴展點名。
     */
    String value() default "";

}

  dubbo默認的狀況下,會依次從下面幾個文件中讀取擴展點。1.META-INF/dubbo/internal/   //dubbo內部實現的各類擴展都放在了這個目錄了。2.META-INF/dubbo/。3.META-INF/services/。只有打了@SPI註解的接口類dubbo纔會去查找擴展點實現。web

  咱們以Protocol爲例,Protocol接口上打了SPI註解,默認的擴展點名稱爲dubboredis

@SPI("dubbo")
public interface Protocol {

      int getDefaultPort();

      @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

     @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

    void destroy();

}

  dubbo中內置了各類協議,如DubboProtocol,HttpProtocol,HessianProtocol等等。Dubbo默認rpc模塊默認protocol實現DubboProtocol,key爲dubbospring

ExtensionLoader類

1.ExtensionLoder.getExtensionLoader(Class<T> type)方法緩存

  每一個定義的SPI接口,都會建立一個ExtensionLoader實例,存儲在ConcurrentMap<Class<?>,ExtensionLoader<?>> EXTENSION_LOADERS這個map對象中app

2.ExtensionLoader使用loadExtensionClasses方法讀取擴展點中的實現類工具

  loadExtensionClasses先讀取SPI註解的value值,若是value有值,就把這個值做爲默認擴展實現的key。而後再以此讀取META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/下對應的文件。ui

3.咱們以Protocal爲例,  loadFile逐行讀取com.alibaba.dubbo.rpc.Protocol文件中的內容,每行內容以key/value形式存儲。先判斷實現類上是否打上了@Adaptive註解,若是打上了該註解,將此類做爲Protocol協議的設配類緩存起來,讀取下一行。若是實現類上沒有打上@Adaptive註解,判斷實現類是否存在參數爲該接口的構造器,有的話做爲包裝類存儲在該ExtensionLoader的Set<Class<?>> cachedWrapperClasses;集合中,這裏用到了裝飾器模式。若是該類既不是設配類,也不是wrapper對象,那就是擴展點的具體實現對象,查找實現類上是否打了@Activate註解,有緩存到變量cachedActivates的map中將實現類緩存到cachedClasses中,以便於使用時獲取。如ProtocolFilterWrapper的實現以下:this

public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolFilterWrapper(Protocol protocol) {
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }
  ..........      
}

4.獲取或則建立設配對象getAdaptiveExtensionurl

  若是cachedAdaptiveClass有值,說明有且僅有一個實現類打了@Adaptive, 實例化這個對象返回。若是cachedAdaptiveClass爲空, 建立設配類字節碼。

  爲何要建立設配類,一個接口多種實現,SPI機制也是如此,這是策略模式,可是咱們在代碼執行過程當中選擇哪一種具體的策略呢。Dubbo採用統一數據模式com.alibaba.dubbo.common.URL(它是dubbo定義的數據模型不是jdk的類),它會穿插於系統的整個執行過程,URL中定義的協議類型字段protocol,會根據具體業務設置不一樣的協議。url.getProtocol()值能夠是dubbo也是能夠webservice, 能夠是zookeeper也能夠是redis。

  設配類的做用是根據url.getProtocol()的值extName,去ExtensionLoader. getExtension( extName)選取具體的擴展點實現。

  有上述的分析可知,可以使用javasist生成設配類的條件:

    1)接口方法中必須至少有一個方法打上了@Adaptive註解

    2)打上了@Adaptive註解的方法參數必須有URL類型參數或者有參數中存在getURL()方法

  createAdaptiveExtensionClass方法源碼以下:

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() + "$Adaptive" + " 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();
    }

5. 經過createAdaptiveExtensionClassCode()生成的java源代碼,要被java虛擬機加載執行必須得編譯成字節碼,dubbo提供兩種方式去執行代碼的編譯1)利用JDK工具類編譯2)利用javassit根據源代碼生成字節碼。

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);
    }

在此順便介紹下@Adaptive註解打在實現類上跟打在接口方法上的區別:

  若是有打在接口方法上,調ExtensionLoader.getAdaptiveExtension()獲取設配類,會先經過前面的過程生成java的源代碼,在經過編譯器編譯成class加載。可是Compiler的實現策略選擇也是經過ExtensionLoader.getAdaptiveExtension(),若是也經過編譯器編譯成class文件那豈不是要死循環下去了嗎?

  ExtensionLoader.getAdaptiveExtension(),對於有實現類上去打了註解@Adaptive的dubbo spi擴展機制,它獲取設配類不在經過前面過程生成設配類java源代碼,而是在讀取擴展文件的時候遇到實現類打了註解@Adaptive就把這個類做爲設配類緩存在ExtensionLoader中,調用是直接返回

6.  自動Wrap上擴展點的Wrap類

  Dubbo是如何自動的給擴展點wrap上裝飾對象的呢?

  在ExtensionLoader.loadFile加載擴展點配置文件的時候對擴展點類有接口類型爲參數的構造器就是包轉對象,緩存到集合中去。

  在調ExtensionLoader的createExtension(name)根據擴展點key建立擴展的時候, 先實例化擴展點的實現, 在判斷時候有此擴展時候有包裝類緩存,有的話利用包轉器加強這個擴展點實現的功能。具體實現以下:

  

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);
        }
    }

7.ioc是spring的三大基礎功能之一, dubbo的ExtensionLoader在加載擴展實現的時候內部實現了個簡單的ioc機制來實現對擴展實現所依賴的參數的注入,         dubbo對擴展實現中公有的set方法且入參個數爲一個的方法,嘗試從對象工廠ObjectFactory獲取值注入到擴展點實現中去。

private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

  下面咱們來看看ObjectFactory是如何根據類型和名字來獲取對象的,ObjectFactory也是基於dubbo的spi擴展機制的。它跟Compiler接口同樣設配類註解@Adaptive是打在類AdaptiveExtensionFactory上的不是經過javassist編譯生成的。

  AdaptiveExtensionFactory持有全部ExtensionFactory對象的集合,dubbo內部默認實現的對象工廠是SpiExtensionFactory和SpringExtensionFactory,他們通過TreeMap排好序的查找順序是優先先從SpiExtensionFactory獲取,若是返回空在從SpringExtensionFactory獲取。

  SpiExtensionFactory工廠獲取要被注入的對象,就是要獲取dubbo spi擴展的實現,因此傳入的參數類型必須是接口類型而且接口上打上了@SPI註解,返回的是一個設配類對象

public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (loader.getSupportedExtensions().size() > 0) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

  SpringExtensionFactory,Dubbo利用spring的擴展機制跟spring作了很好的融合。在發佈或者去引用一個服務的時候,會把spring的容器添加到SpringExtensionFactory工廠集合中去, 當SpiExtensionFactory沒有獲取到對象的時候會遍歷SpringExtensionFactory中的spring容器來獲取要注入的對象

public <T> T getExtension(Class<T> type, String name) {
        for (ApplicationContext context : contexts) {
            if (context.containsBean(name)) {
                Object bean = context.getBean(name);
                if (type.isInstance(bean)) {
                    return (T) bean;
                }
            }
        }
        return null;
    }

  ExtensionLoader總體活動圖以下:

相關文章
相關標籤/搜索