聊聊Dubbo(五):核心原始碼-SPI擴充套件

猿碼道發表於2018-04-12

0 前言

站在一個框架作者的角度來說,定義一個介面,自己預設給出幾個介面的實現類,同時 允許框架的使用者也能夠自定義介面的實現。現在一個簡單的問題就是:如何優雅的根據一個介面來獲取該介面的所有實現類呢?

JDK SPI 正是為了優雅解決這個問題而生,SPI 全稱為 (Service Provider Interface),即服務提供商介面,是JDK內建的一種服務提供發現機制。目前有不少框架用它來做服務的擴充套件發現,簡單來說,它就是一種動態替換發現服務實現者的機制

所以,Dubbo如此被廣泛接納的其中的 一個重要原因就是基於SPI實現的強大靈活的擴充套件機制,開發者可自定義外掛嵌入Dubbo,實現靈活的業務需求。

有人會覺得這就是建立在面向介面程式設計下的一種為了使元件可擴充套件或動態變更實現的規範,常見的類SPI的設計有 JDBC、JNDI、JAXP 等,很多開源框架的內部實現也採用了SPI。例如:JDBC的架構是由一套API組成,用於給Java應用提供訪問不同資料庫的能力,而資料庫提供商的驅動軟體各不相同,JDBC通過提供一套通用行為的API介面,底層可以由提供商自由實現,雖然JDBC的設計沒有指明是SPI,但也和SPI的設計類似。

1 JDK SPI擴充套件

JDK為SPI的實現提供了工具類,即java.util.ServiceLoader,ServiceLoader中定義的SPI規範沒有什麼特別之處,只需要有一個提供者配置檔案(provider-configuration file),該檔案需要在resource目錄META-INF/services下,檔名就是服務介面的全限定名

  1. 檔案內容是提供者Class的全限定名列表,顯然提供者Class都應該實現服務介面;
  2. 檔案必須使用UTF-8編碼

1.1 JDK SPI 示例

程式碼示例

/**
 * SPI服務介面
 */
public interface Cmand {
    public void execute();
}
public class ShutdownCommand implements Cmand {
    public void execute() {
        System.out.println("shutdown....");  
    }
}
public class StartCommand implements Cmand {
    public void execute() {
        System.out.println("start....");
    }
}
public class SPIMain {
    public static void main(String[] args) {
        ServiceLoader<Cmand> loader = ServiceLoader.load(Cmand.class);
        System.out.println(loader);
 
        for (Cmand Cmand : loader) {
            Cmand.execute();
        }
    }
}
複製程式碼

META-INF/services/com.unei.serviceloader.Cmand檔案中配置:

com.unei.serviceloader.impl.ShutdownCommand  
com.unei.serviceloader.impl.StartCommand 
複製程式碼

執行結果:

java.util.ServiceLoader[com.unei.serviceloader.Cmand]
shutdown....
start....
複製程式碼

1.2 JDK SPI 原理

  1. 配置檔案為什麼要放在META-INF/services下面?

    ServiceLoader類定義如下:

    private static final String PREFIX = "META-INF/services/"; (JDK已經寫死了)
    複製程式碼

    但是 如果ServiceLoader在load時提供Classloader,則可以從其他的目錄讀取。

  2. ServiceLoader讀取實現類是什麼時候例項化的?來看下ServiceLoader的幾個重要屬性:

    
    private static final String PREFIX = "META-INF/services/";
    
    // 要載入的介面
    private Class<S> service;
    
    // The class loader used to locate, load, and instantiate providers
    private ClassLoader loader;
    
    // 用於快取已經載入的介面實現類,其中key為實現類的完整類名
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    
    // 用於延遲載入介面的實現類
    private LazyIterator lookupIterator;
    
    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }
    
    private class LazyIterator implements Iterator<S> {
    
        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;
    
        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }
    
        public boolean hasNext() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                       configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }
    
        public S next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                // 遍歷時,查詢類物件
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,  "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service, "Provider " + cn  + " not a subtype");
            }
            try {
                // 遍歷時,才會初始化類例項物件
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service, "Provider " + cn + " could not be instantiated", x);
            }
            throw new Error();
        }
    
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
    複製程式碼

1.3 JDK SPI ServiceLoader缺點

  1. 雖然ServiceLoader也算是使用的延遲載入,但是基本只能通過遍歷全部獲取,也就是介面的實現類全部載入並例項化一遍。如果你並不想用某些實現類,它也被載入並例項化了,這就造成了浪費。
  2. 獲取某個實現類的方式不夠靈活,只能通過Iterator形式獲取,不能根據某個引數來獲取對應的實現類。

2 Dubbo SPI擴充套件

Dubbo對JDK SPI進行了擴充套件,對服務提供者配置檔案中的內容進行了改造,由原來的提供者類的全限定名列表改成了KV形式的列表,這也導致了Dubbo中無法直接使用JDK ServiceLoader,所以,與之對應的,在Dubbo中有ExtensionLoader,ExtensionLoader是擴充套件點載入器,用於載入Dubbo中的各種可配置元件,比如:動態代理方式(ProxyFactory)、負載均衡策略(LoadBalance)、RCP協議(Protocol)、攔截器(Filter)、容器型別(Container)、叢集方式(Cluster)和註冊中心型別(RegistryFactory)等

總之,Dubbo為了應對各種場景,它的所有內部元件都是通過這種SPI的方式來管理的,這也是為什麼Dubbo需要將服務提供者配置檔案設計成KV鍵值對形式,這個K就是我們在Dubbo配置檔案或註解中用到的K,Dubbo直接通過服務介面(上面提到的ProxyFactory、LoadBalance、Protocol、Filter等)和配置的K從ExtensionLoader拿到服務提供的實現類

同時,由於Dubbo使用了URL匯流排的設計,即很多引數通過URL物件來傳遞,在實際中,具體要用到哪個值,可以通過URL中的引數值來指定

2.1 擴充套件功能介紹

Dubbo對SPI的擴充套件是 通過ExtensionLoader來實現的,檢視ExtensionLoader的原始碼,可以看到Dubbo對JDK SPI 做了三個方面的擴充套件:

  1. 方便獲取擴充套件實現:JDK SPI僅僅通過介面類名獲取所有實現,而ExtensionLoader則通過介面類名和key值獲取一個實現

  2. IOC依賴注入功能:Adaptive實現,就是生成一個代理類,這樣就可以根據實際呼叫時的一些引數動態決定要呼叫的類了

    舉例來說:介面A,實現者A1、A2。介面B,實現者B1、B2。

    現在實現者A1含有setB()方法,會自動注入一個介面B的實現者,此時注入B1還是B2呢?都不是,而是注入一個動態生成的介面B的實現者B$Adpative,該實現者能夠根據引數的不同,自動引用B1或者B2來完成相應的功能

  3. 採用裝飾器模式進行功能增強,自動包裝實現,這種實現的類一般是自動啟用的,常用於包裝類,比如:Protocol的兩個實現類:ProtocolFilterWrapper、ProtocolListenerWrapper。

    還是第2個的例子,介面A的另一個實現者AWrapper1。大體內容如下:

    private A a;
    AWrapper1(A a){
    	  this.a=a;
    }
    複製程式碼

    因此,當在獲取某一個介面A的實現者A1的時候,已經自動被AWrapper1包裝了

2.2 擴充套件原始碼分析

2.2.1 ExtensionLoader初始化

以獲取DubboProtocol為例

@SPI("dubbo")
public interface Protocol {
    
    /**
     * 獲取預設埠,當使用者沒有配置埠時使用。
     * 
     * @return 預設埠
     */
    int getDefaultPort();

    /**
     * 暴露遠端服務:<br>
     * 1. 協議在接收請求時,應記錄請求來源方地址資訊:RpcContext.getContext().setRemoteAddress();<br>
     * 2. export()必須是冪等的,也就是暴露同一個URL的Invoker兩次,和暴露一次沒有區別。<br>
     * 3. export()傳入的Invoker由框架實現並傳入,協議不需要關心。<br>
     * 
     * @param <T> 服務的型別
     * @param invoker 服務的執行體
     * @return exporter 暴露服務的引用,用於取消暴露
     * @throws RpcException 當暴露服務出錯時丟擲,比如埠已佔用
     */
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    /**
     * 引用遠端服務:<br>
     * 1. 當使用者呼叫refer()所返回的Invoker物件的invoke()方法時,協議需相應執行同URL遠端export()傳入的Invoker物件的invoke()方法。<br>
     * 2. refer()返回的Invoker由協議實現,協議通常需要在此Invoker中傳送遠端請求。<br>
     * 3. 當url中有設定check=false時,連線失敗不能丟擲異常,並內部自動恢復。<br>
     * 
     * @param <T> 服務的型別
     * @param type 服務的型別
     * @param url 遠端服務的URL地址
     * @return invoker 服務的本地代理
     * @throws RpcException 當連線服務提供方失敗時丟擲
     */
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

    /**
     * 釋放協議:<br>
     * 1. 取消該協議所有已經暴露和引用的服務。<br>
     * 2. 釋放協議所佔用的所有資源,比如連線和埠。<br>
     * 3. 協議在釋放後,依然能暴露和引用新的服務。<br>
     */
    void destroy();

}

public class DubboProtocol extends AbstractProtocol {

    public static final String NAME = "dubbo";
    ...
    ...
}

// 示例:
ExtensionLoader<Protocol> protocolLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol dubboProtocol = protocolLoader.getExtension(DubboProtocol.NAME);
複製程式碼
  1. ExtensionLoader.getExtensionLoader(Protocol.class):獲取ExtensionLoader例項

    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
    
    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!");
         }
         // 0. 判斷是否為通過SPI註解定義的可擴充套件介面
         if(!withExtensionAnnotation(type)) {
             throw new IllegalArgumentException("Extension type(" + type + 
                     ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
         }
         // 1. 先從EXTENSION_LOADERS中,根據傳入可擴充套件型別type查詢
         ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
         if (loader == null) {
             // 2. 不存在,則新建ExtensionLoader例項
             EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
             loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
         }
         return loader;
    }
    
     private ExtensionLoader(Class<?> type) {
         this.type = type;
         objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
     }
    
     private static <T> boolean withExtensionAnnotation(Class<T> type) {
         return type.isAnnotationPresent(SPI.class);
     }
    複製程式碼

    ExtensionLoader充當外掛工廠角色,提供了一個私有的構造器。其入參type為擴充套件介面型別。Dubbo通過SPI註解定義了可擴充套件的介面,如Filter、Transporter等。每個型別的擴充套件對應一個ExtensionLoader。SPI的value引數決定了預設的擴充套件實現

    當擴充套件型別是ExtensionFactory時,不指定objectFactory,否則初始化ExtensionFactory的ExtensionLoader並獲取一個擴充套件介面卡

  2. protocolLoader.getExtension(DubboProtocol.NAME):根據Key獲取相應的擴充套件實現類例項

     /**
      * 返回指定名字的擴充套件。如果指定名字的擴充套件不存在,則拋異常 {@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();
         }
         // 1. 先從快取中取相應的擴充套件實現類例項
         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) {
                     // 2. 建立相應的擴充套件實現類例項
                     instance = createExtension(name);
                     holder.set(instance);
                 }
             }
         }
         return (T) instance;
    }
    
     @SuppressWarnings("unchecked")
     private T createExtension(String name) {
         // 3. 根據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);
         }
     }   
    複製程式碼

    createExtension方法就是建立相應的擴充套件類例項,具體裡面的建立步驟下文會具體說到,接下來先繼續深入看getExtensionClasses方法是如何從配置檔案中找到相應的擴充套件類的類配置

2.2.2 配置檔案掃描

Dubbo預設依次掃描**META-INF/dubbo/internal/、META-INF/dubbo/、META-INF/services/三個classpath目錄下的配置檔案**。配置檔案以具體擴充套件介面全名命名,如:com.alibaba.dubbo.rpc.Filter,內容如下:

# 等號前為副檔名,其後為擴充套件實現類全路徑名
echo=com.alibaba.dubbo.rpc.filter.EchoFilter
generic=com.alibaba.dubbo.rpc.filter.GenericFilter
genericimpl=com.alibaba.dubbo.rpc.filter.GenericImplFilter
token=com.alibaba.dubbo.rpc.filter.TokenFilter
accesslog=com.alibaba.dubbo.rpc.filter.AccessLogFilter
activelimit=com.alibaba.dubbo.rpc.filter.ActiveLimitFilter
classloader=com.alibaba.dubbo.rpc.filter.ClassLoaderFilter
context=com.alibaba.dubbo.rpc.filter.ContextFilter
consumercontext=com.alibaba.dubbo.rpc.filter.ConsumerContextFilter
exception=com.alibaba.dubbo.rpc.filter.ExceptionFilter
executelimit=com.alibaba.dubbo.rpc.filter.ExecuteLimitFilter
deprecated=com.alibaba.dubbo.rpc.filter.DeprecatedFilter
compatible=com.alibaba.dubbo.rpc.filter.CompatibleFilter
timeout=com.alibaba.dubbo.rpc.filter.TimeoutFilter
monitor=com.alibaba.dubbo.monitor.support.MonitorFilter
validation=com.alibaba.dubbo.validation.filter.ValidationFilter
cache=com.alibaba.dubbo.cache.filter.CacheFilter
trace=com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilter
future=com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter
複製程式碼

從上一小節的,getExtensionClasses()方法原始碼看起:

	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) {
                // 1. 逐行讀取配置檔案,提取出副檔名或擴充套件類路徑
                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) {
                                            // 2. 利用Class.forName方法進行類載入
                                            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.");
                                            }
                                            // 3. 處理Adaptive註解,若存在則將該實現類儲存至cachedAdaptiveClass屬性
                                            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 {
                                                    // 4. 嘗試獲取引數型別為當前擴充套件型別的構造器方法,若成功則表明存在該擴充套件的封裝型別,將封裝型別存入wrappers集合;否則轉入第五步
                                                    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);
                                                            }
                                                        }
                                                    }

                                                    // 5. 處理active註解,將副檔名對應active註解存入cachedActivates
                                                    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. 利用Class.forName方法進行類載入;

    Class<?> clazz = Class.forName(line, true, classLoader);
    複製程式碼
  3. 處理Adaptive註解,若存在則將該實現類儲存至cachedAdaptiveClass屬性

    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());
       }
    }
    複製程式碼
  4. 嘗試獲取引數型別為當前擴充套件型別的構造器方法,若成功則表明存在該擴充套件的封裝型別,將封裝型別存入wrappers集合;否則丟擲異常轉入第五步;

    try {
        // 擴充套件型別引數的構造器是封裝器的約定特徵,目前dubbo中預設的只有Filter和Listener的封裝器
        clazz.getConstructor(type); 
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }
        wrappers.add(clazz);
    } catch (NoSuchMethodException e) {
        // 第五步
    }
    複製程式碼
  5. 處理active註解,將副檔名對應active註解存入cachedActivates;

    Activate activate = clazz.getAnnotation(Activate.class);
    if (activate != null) {
        cachedActivates.put(names[0], activate);
    }
    複製程式碼

2.2.3 擴充套件介面卡

在dubbo擴充套件中,介面卡模式被廣泛使用,其作用在於為同一擴充套件型別下的多個擴充套件實現的呼叫提供路由功能,如指定優先順序等。dubbo提供了兩種方式來生成擴充套件介面卡:

  1. 靜態介面卡擴充套件

    所謂的靜態介面卡擴充套件就是提前通過編碼的形式確定擴充套件的具體實現,且該實現類由Adaptive註解標註,如:AdaptiveCompiler。在載入配置檔案的loadFile方法中,已經描述過處理該型別擴充套件的邏輯,具體可參考上一小節loadFile()方法原始碼

    @Adaptive
    public class AdaptiveCompiler implements Compiler {
    
        private static volatile String DEFAULT_COMPILER;
    
        public static void setDefaultCompiler(String compiler) {
            DEFAULT_COMPILER = compiler;
        }
    
        public Class<?> compile(String code, ClassLoader classLoader) {
            Compiler compiler;
            ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
            String name = DEFAULT_COMPILER; // copy reference
            if (name != null && name.length() > 0) {
                compiler = loader.getExtension(name);
            } else {
                compiler = loader.getDefaultExtension();
            }
            return compiler.compile(code, classLoader);
        }
    }
    複製程式碼
  2. 動態介面卡擴充套件

    動態介面卡擴充套件即通過動態代理生成擴充套件類的動態代理類,在dubbo中是通過javassist技術生成的。與傳統的jdk動態代理、cglib不同,javassist提供封裝後的API對位元組碼進行間接操作,簡單易用,不關心具體位元組碼,靈活性更高,且處理效率也較高,是dubbo預設的編譯器。

首先,從ExtensionLoader構造器中會呼叫getAdaptiveExtension()方法觸發為當前擴充套件型別生成介面卡,原始碼如下:

    private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

    public T getAdaptiveExtension() {
        // 1. 首先,檢查是否存在當前擴充套件類靜態介面卡
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if(createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            // 2. 建立當前擴充套件類動態介面卡
                            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;
    }

    private T createAdaptiveExtension() {
        try {
            // IOC屬性注入
            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();
        // 得到Adaptive類程式碼內容,通過Compiler進行編譯和類載入
        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;
        // 1. 檢查是否至少有一個方法有Adaptive註解,若不存在則丟擲異常,即要完成動態代理,必須有方法標註了Adaptive註解
        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;
                    }
                }
                // 對於有Adaptive註解的方法,判斷其入參中是否有URL型別的引數,或者複雜引數中是否有URL型別的屬性,若沒有則丟擲異常。
                // 這裡體現出了為什麼dubbo要提供動態介面卡生成機制。dubbo中的URL匯流排提供了服務的全部資訊,而開發者可以定義差異化的服務配置,因此生成的URL差異化也較大,若全部靠使用者硬編碼靜態介面卡的話效率太低。
                // 有了動態代理,dubbo可以根據URL引數動態地生成介面卡的適配邏輯,確定擴充套件實現的獲取優先順序。因此,URL作為引數直接或間接傳入是必須的,否則失去了動態生成的憑據。
                // 有型別為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();
    }
複製程式碼

根據adaptive註解的value陣列值,及SPI註解定義的預設副檔名,確定適配邏輯,即擴充套件獲取的優先順序,這裡不再羅列程式碼,下面為一個具體生成的介面卡原始碼:

package com.alibaba.dubbo.remoting;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Transporter$Adpative implements com.alibaba.dubbo.remoting.Transporter {
    public com.alibaba.dubbo.remoting.Client connect(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.common.URL {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("client", url.getParameter("transporter", "netty")); // 處理順序
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        return extension.connect(arg0, arg1);
    }
    public com.alibaba.dubbo.remoting.Server bind(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.common.URL {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("server", url.getParameter("transporter", "netty")); // 處理順序
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        return extension.bind(arg0, arg1);
    }
}
複製程式碼

可以看到,核心邏輯是獲取副檔名extName,以bind方法為例,其獲取優先順序是server,transporter,netty,可參見URL的getParameter方法原始碼。其中netty是Transporter介面的SPI註解確定的預設值,而server和transporter是bind方法的Adaptive註解定義的。

@SPI("netty")
public interface Transporter {
    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
    Server bind(URL url, ChannelHandler handler) throws RemotingException;
    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;
}
複製程式碼

拿到副檔名後,再從ExtensionLoader獲取到擴充套件例項,呼叫具體的bind方法

原始碼生成後,ExtensionLoader再呼叫預設的JavassitCompiler進行編譯和類載入,其具體實現原理不在本文討論範圍,有機會的話後續會介紹這部分內容。

綜上可知,ExtensionLoader提供了獲取擴充套件介面卡的方法,優先檢視是否有靜態介面卡,否則會使用動態介面卡

2.2.4 封裝類

dubbo中存在 一種對於擴充套件的封裝類,其功能是將各擴充套件例項串聯起來,形成擴充套件鏈,比如過濾器鏈,監聽鏈。當呼叫ExtensionLoader的getExtension方法時,會做攔截處理,如果存在封裝器,則返回封裝器實現,而將真實實現通過構造方法注入到封裝器中

    @SuppressWarnings("unchecked")
    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);
            }
            // IOC 注入
            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);
        }
    }
    
    private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                // 遍歷當前例項所有方法,判斷是否需要進行set屬性注入
                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) : "";
                            // 通過ExtensionFactory獲取被注入set屬性例項
                            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;
    }
複製程式碼

這裡有個injectExtension方法,其作用是:

如果當前擴充套件例項存在其他的擴充套件屬性,則通過反射呼叫其set方法設定擴充套件屬性。若該擴充套件屬性是介面卡型別,也是通過ExtensionLoader獲取的。

所以,ExtensionLoader作為一個IOC外掛容器,為dubbo的外掛體系運作提供了保障,可以說是dubbo中的核心。掌握了其基本原理,才有助於我們更好地分析dubbo原始碼。

相關文章