Dubbo(二):深刻理解Dubbo的服務發現SPI機制

1、前言

  用到微服務就不得不來談談服務發現的話題。通俗的來講,就是在提供服務方把服務註冊到註冊中心,而且告訴服務消費方如今已經存在了這個服務。那麼裏面的細節究竟是怎麼經過代碼實現的呢,如今咱們來看看Dubbo中的SPI機制java

2、SPI簡介

  SPI 全稱爲 Service Provider Interface,是一種服務發現機制。SPI本質是將接口實現類的全限定名配置在文件中,並由服務加載器讀取配置文件,加載實現類,這樣運行時能夠動態的爲接口替換實現類數組

3、Dubbo中的SPI

  Dubbo與上面的普通的Java方式實現SPI不一樣,在Dubbo中從新實現了一套功能更強的SPI機制,即經過鍵值對的方式進行配置及緩存。其中也使用ConcurrentHashMap與synchronize防止併發問題出現。主要邏輯封裝在ExtensionLoader中。下面咱們看看源碼。緩存

4、ExtensionLoader源碼解析

  因爲內部的方法實在太多,咱們只挑選與實現SPI的重要邏輯部分拿出來說解。  併發

  一、getExtensionLoader(Class<T> type)app

 1 public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
 2         if (type == null) {
 3             throw new IllegalArgumentException("Extension type == null");
 4         } else if (!type.isInterface()) {
 5             throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
 6         } else if (!withExtensionAnnotation(type)) {
 7             throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
 8         } else {
 9             ExtensionLoader<T> loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
10             if (loader == null) {
11                 EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
12                 loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
13             }
14 
15             return loader;
16         }
17     }
View Code

  這個是能夠將對應的接口轉換爲ExtensionLoader 實例。至關於告訴Dubbo這是個服務接口,裏面有對應的服務提供者框架

  先是邏輯判斷傳進來的類不能爲空,必須是接口且被@SPI註解註釋過。這三個條件都知足就會建立ExtensionLoader 實例。一樣的,若是當前類已經被建立過ExtensionLoader 實例,那麼直接拿取。不然新建一個。這裏使用的是鍵值對的存儲類型,以下圖:ide

 

 

   使用ConcurrentHashMap防止在併發時出現問題,而且效率高HashTable很多,因此咱們平常項目併發場景中也應該多用ConcurrentHashMap進行存儲。微服務

 

  二、getExtension(String name)學習

 1 public T getExtension(String name) {
 2     if (name == null || name.length() == 0)
 3         throw new IllegalArgumentException("Extension name == null");
 4     if ("true".equals(name)) {
 5         // 獲取默認的拓展實現類
 6         return getDefaultExtension();
 7     }
 8     // Holder,顧名思義,用於持有目標對象
 9     Holder<Object> holder = cachedInstances.get(name);
10     if (holder == null) {
11         cachedInstances.putIfAbsent(name, new Holder<Object>());
12         holder = cachedInstances.get(name);
13     }
14     Object instance = holder.get();
15     // 雙重檢查
16     if (instance == null) {
17         synchronized (holder) {
18             instance = holder.get();
19             if (instance == null) {
20                 // 建立拓展實例
21                 instance = createExtension(name);
22                 // 設置實例到 holder 中
23                 holder.set(instance);
24             }
25         }
26     }
27     return  instance;
28 }
View Code

  這個方法主要是至關於獲得具體的服務,上述咱們已經對服務的接口進行加載,如今咱們須要調用服務接口下的某一個具體服務實現類。就用這個方法。上述方法能夠看出是會進入getOrCreateHolder中,這個方法顧名思義是獲取或者建立Holder。進入到下面方法中:this

 1 private Holder<Object> getOrCreateHolder(String name) {
 2         //檢查緩存中是否存在
 3         Holder<Object> holder = (Holder)this.cachedInstances.get(name);
 4         if (holder == null) {
 5         //緩存中不存在就去建立一個新的Holder
 6             this.cachedInstances.putIfAbsent(name, new Holder());
 7             holder = (Holder)this.cachedInstances.get(name);
 8         }
 9 
10         return holder;
11     }
View Code

  一樣,緩存池也是以ConcurrentHashMap爲存儲結構

 

 

   三、createExtension(String name)

  實際上getExtension方法不必定每次都能拿到,當服務實現類是第一次進行加載的時候就須要當前的方法

 1 private T createExtension(String name) {
 2         Class<?> clazz = (Class)this.getExtensionClasses().get(name);
 3         if (clazz == null) {
 4             throw this.findException(name);
 5         } else {
 6             try {
 7                 T instance = EXTENSION_INSTANCES.get(clazz);
 8                 if (instance == null) {
 9                     EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
10                     instance = EXTENSION_INSTANCES.get(clazz);
11                 }
12 
13                 this.injectExtension(instance);
14                 Set<Class<?>> wrapperClasses = this.cachedWrapperClasses;
15                 Class wrapperClass;
16                 if (CollectionUtils.isNotEmpty(wrapperClasses)) {
17                     for(Iterator var5 = wrapperClasses.iterator(); var5.hasNext(); instance = this.injectExtension(wrapperClass.getConstructor(this.type).newInstance(instance))) {
18                         wrapperClass = (Class)var5.next();
19                     }
20                 }
21 
22                 return instance;
23             } catch (Throwable var7) {
24                 throw new IllegalStateException("Extension instance (name: " + name + ", class: " + this.type + ") couldn't be instantiated: " + var7.getMessage(), var7);
25             }
26         }
27     }
View Code

  能夠看出createExtension其實是一個私有方法,也就是由上面的getExtension自動觸發。內部邏輯大體爲:

    3.一、經過 getExtensionClasses 獲取全部的拓展類

    3.二、經過反射建立拓展對象

    3.三、向拓展對象中注入依賴(這裏Dubbo有單獨的IOC後面會介紹)

    3.四、將拓展對象包裹在相應的 Wrapper 對象中

  四、getExtensionClasses()

  1 private Map<String, Class<?>> getExtensionClasses() {
  2     // 從緩存中獲取已加載的拓展類
  3     Map<String, Class<?>> classes = cachedClasses.get();
  4     // 雙重檢查
  5     if (classes == null) {
  6         synchronized (cachedClasses) {
  7             classes = cachedClasses.get();
  8             if (classes == null) {
  9                 // 加載拓展類
 10                 classes = loadExtensionClasses();
 11                 cachedClasses.set(classes);
 12             }
 13         }
 14     }
 15     return classes;
 16 }
 17 
 18 //進入到loadExtensionClasses中
 19 
 20 private Map<String, Class<?>> loadExtensionClasses() {
 21     // 獲取 SPI 註解,這裏的 type 變量是在調用 getExtensionLoader 方法時傳入的
 22     final SPI defaultAnnotation = type.getAnnotation(SPI.class);
 23     if (defaultAnnotation != null) {
 24         String value = defaultAnnotation.value();
 25         if ((value = value.trim()).length() > 0) {
 26             // 對 SPI 註解內容進行切分
 27             String[] names = NAME_SEPARATOR.split(value);
 28             // 檢測 SPI 註解內容是否合法,不合法則拋出異常
 29             if (names.length > 1) {
 30                 throw new IllegalStateException("more than 1 default extension name on extension...");
 31             }
 32 
 33             // 設置默認名稱,參考 getDefaultExtension 方法
 34             if (names.length == 1) {
 35                 cachedDefaultName = names[0];
 36             }
 37         }
 38     }
 39 
 40     Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
 41     // 加載指定文件夾下的配置文件
 42     loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
 43     loadDirectory(extensionClasses, DUBBO_DIRECTORY);
 44     loadDirectory(extensionClasses, SERVICES_DIRECTORY);
 45     return extensionClasses;
 46 }
 47 
 48 //進入到loadDirectory中
 49 
 50 private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
 51     // fileName = 文件夾路徑 + type 全限定名 
 52     String fileName = dir + type.getName();
 53     try {
 54         Enumeration<java.net.URL> urls;
 55         ClassLoader classLoader = findClassLoader();
 56         // 根據文件名加載全部的同名文件
 57         if (classLoader != null) {
 58             urls = classLoader.getResources(fileName);
 59         } else {
 60             urls = ClassLoader.getSystemResources(fileName);
 61         }
 62         if (urls != null) {
 63             while (urls.hasMoreElements()) {
 64                 java.net.URL resourceURL = urls.nextElement();
 65                 // 加載資源
 66                 loadResource(extensionClasses, classLoader, resourceURL);
 67             }
 68         }
 69     } catch (Throwable t) {
 70         logger.error("...");
 71     }
 72 }
 73 
 74 //進入到loadResource中
 75 
 76 private void loadResource(Map<String, Class<?>> extensionClasses, 
 77     ClassLoader classLoader, java.net.URL resourceURL) {
 78     try {
 79         BufferedReader reader = new BufferedReader(
 80             new InputStreamReader(resourceURL.openStream(), "utf-8"));
 81         try {
 82             String line;
 83             // 按行讀取配置內容
 84             while ((line = reader.readLine()) != null) {
 85                 // 定位 # 字符
 86                 final int ci = line.indexOf('#');
 87                 if (ci >= 0) {
 88                     // 截取 # 以前的字符串,# 以後的內容爲註釋,須要忽略
 89                     line = line.substring(0, ci);
 90                 }
 91                 line = line.trim();
 92                 if (line.length() > 0) {
 93                     try {
 94                         String name = null;
 95                         int i = line.indexOf('=');
 96                         if (i > 0) {
 97                             // 以等於號 = 爲界,截取鍵與值
 98                             name = line.substring(0, i).trim();
 99                             line = line.substring(i + 1).trim();
100                         }
101                         if (line.length() > 0) {
102                             // 加載類,並經過 loadClass 方法對類進行緩存
103                             loadClass(extensionClasses, resourceURL, 
104                                       Class.forName(line, true, classLoader), name);
105                         }
106                     } catch (Throwable t) {
107                         IllegalStateException e = new IllegalStateException("Failed to load extension class...");
108                     }
109                 }
110             }
111         } finally {
112             reader.close();
113         }
114     } catch (Throwable t) {
115         logger.error("Exception when load extension class...");
116     }
117 }
118 
119 //進入到loadClass中
120 
121 private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, 
122     Class<?> clazz, String name) throws NoSuchMethodException {
123     
124     if (!type.isAssignableFrom(clazz)) {
125         throw new IllegalStateException("...");
126     }
127 
128     // 檢測目標類上是否有 Adaptive 註解
129     if (clazz.isAnnotationPresent(Adaptive.class)) {
130         if (cachedAdaptiveClass == null) {
131             // 設置 cachedAdaptiveClass緩存
132             cachedAdaptiveClass = clazz;
133         } else if (!cachedAdaptiveClass.equals(clazz)) {
134             throw new IllegalStateException("...");
135         }
136         
137     // 檢測 clazz 是不是 Wrapper 類型
138     } else if (isWrapperClass(clazz)) {
139         Set<Class<?>> wrappers = cachedWrapperClasses;
140         if (wrappers == null) {
141             cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
142             wrappers = cachedWrapperClasses;
143         }
144         // 存儲 clazz 到 cachedWrapperClasses 緩存中
145         wrappers.add(clazz);
146         
147     // 程序進入此分支,代表 clazz 是一個普通的拓展類
148     } else {
149         // 檢測 clazz 是否有默認的構造方法,若是沒有,則拋出異常
150         clazz.getConstructor();
151         if (name == null || name.length() == 0) {
152             // 若是 name 爲空,則嘗試從 Extension 註解中獲取 name,或使用小寫的類名做爲 name
153             name = findAnnotationName(clazz);
154             if (name.length() == 0) {
155                 throw new IllegalStateException("...");
156             }
157         }
158         // 切分 name
159         String[] names = NAME_SEPARATOR.split(name);
160         if (names != null && names.length > 0) {
161             Activate activate = clazz.getAnnotation(Activate.class);
162             if (activate != null) {
163                 // 若是類上有 Activate 註解,則使用 names 數組的第一個元素做爲鍵,
164                 // 存儲 name 到 Activate 註解對象的映射關係
165                 cachedActivates.put(names[0], activate);
166             }
167             for (String n : names) {
168                 if (!cachedNames.containsKey(clazz)) {
169                     // 存儲 Class 到名稱的映射關係
170                     cachedNames.put(clazz, n);
171                 }
172                 Class<?> c = extensionClasses.get(n);
173                 if (c == null) {
174                     // 存儲名稱到 Class 的映射關係
175                     extensionClasses.put(n, clazz);
176                 } else if (c != clazz) {
177                     throw new IllegalStateException("...");
178                 }
179             }
180         }
181     }
182 }
View Code

  上面的方法較多,理一下邏輯:

  一、getExtensionClasses():先檢查緩存,若緩存未命中,則經過 synchronized 加鎖。加鎖後再次檢查緩存,並判斷是否爲空。此時若是 classes 仍爲 null,則經過 loadExtensionClasses 加載拓展類。

  二、loadExtensionClasses():SPI 註解的接口進行解析,然後調用 loadDirectory 方法加載指定文件夾配置文件。

  三、loadDirectory():方法先經過 classLoader 獲取全部資源連接,而後再經過 loadResource 方法加載資源。

  四、loadResource():用於讀取和解析配置文件,並經過反射加載類,最後調用 loadClass 方法進行其餘操做。loadClass 方法用於主要用於操做緩存。

  五、小結:

  咱們稍微捋一下Dubbo是如何進行SPI的即發現接口的實現類。先是須要實例化擴展類加載器。這裏爲了更好的和微服務貼合起來,咱們就把它稱做服務加載器。在服務加載器中用的是ConcurrentHashMap的緩存結構。在咱們須要尋找服務的過程當中,Dubbo先經過反射加載類,然後將有@SPI表示的接口(即服務接口)的實現類(即服務提供方)進行配置對應的文件夾及文件。將配置文件以鍵值對的方式存到緩存中key就是當前服務接口下類的名字,value就是Dubbo生成的對應的類配置文件。方便咱們下次調用。其中爲了防止併發問題產生,使用ConcurrentHashMap,而且使用synchronize關鍵字對存在併發問題的節點進行雙重檢查。

5、Dubbo中的IOC

  在createExtension中有提到過將拓展對象注入依賴。這裏使用的是injectExtension(T instance):

 1 private T injectExtension(T instance) {
 2     try {
 3         if (objectFactory != null) {
 4             // 遍歷目標類的全部方法
 5             for (Method method : instance.getClass().getMethods()) {
 6                 // 檢測方法是否以 set 開頭,且方法僅有一個參數,且方法訪問級別爲 public
 7                 if (method.getName().startsWith("set")
 8                     && method.getParameterTypes().length == 1
 9                     && Modifier.isPublic(method.getModifiers())) {
10                     // 獲取 setter 方法參數類型
11                     Class<?> pt = method.getParameterTypes()[0];
12                     try {
13                         // 獲取屬性名,好比 setName 方法對應屬性名 name
14                         String property = method.getName().length() > 3 ? 
15                             method.getName().substring(3, 4).toLowerCase() + 
16                                 method.getName().substring(4) : "";
17                         // 從 ObjectFactory 中獲取依賴對象
18                         Object object = objectFactory.getExtension(pt, property);
19                         if (object != null) {
20                             // 經過反射調用 setter 方法設置依賴
21                             method.invoke(instance, object);
22                         }
23                     } catch (Exception e) {
24                         logger.error("fail to inject via method...");
25                     }
26                 }
27             }
28         }
29     } catch (Exception e) {
30         logger.error(e.getMessage(), e);
31     }
32     return instance;
33 }
View Code

  在上面代碼中,objectFactory 變量的類型爲 AdaptiveExtensionFactory,AdaptiveExtensionFactory 內部維護了一個 ExtensionFactory 列表,用於存儲其餘類型的 ExtensionFactory。Dubbo 目前提供了兩種 ExtensionFactory,分別是 SpiExtensionFactory 和 SpringExtensionFactory。前者用於建立自適應的拓展,後者是用於從 Spring 的 IOC 容器中獲取所需的拓展。這就是咱們常說的Dubbo爲何可以與Spring無縫鏈接,由於Dubbo底層就是依賴Spring的,對於Spring的IOC容器可直接拿來用。

6、總結

  從框架的源碼中若是要繼續深挖的話,能夠多思考思考synchronize用的地方,爲何要用,若是不用的話會有什麼併發問題。Dubbo的服務發現只是爲咱們之後學習Dubbo框架打下基礎,至少讓咱們知道Dubbo是如何進行服務發現的。

相關文章
相關標籤/搜索