日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Dubbo的SPI實現(xiàn)以及與JDK實現(xiàn)的區(qū)別

在 Java 里, 為了規(guī)范開發(fā),制定了大量的「規(guī)范」與「標(biāo)準(zhǔn)」,這些上層的內(nèi)容,大多是以接口的形式提供出來。那這些接口最終實現(xiàn)是誰呢,在哪里呢?

規(guī)范并不關(guān)心這個。

所謂規(guī)范,是指定了一系列內(nèi)容,來指導(dǎo)我們的開發(fā)實現(xiàn)。比如 Servlet規(guī)范對于 Servlet 的行為做了說明,具體實現(xiàn)時,可以是 Tomcat,可以是Jetty 等等。

再比如 Java 的 JDBC 規(guī)范,規(guī)定了 Driver 提供者需要實現(xiàn)的內(nèi)容,但具體是 Oracle,或者M(jìn)ySQL 都可以支持。關(guān)于JDBC 可以看之前一篇文章(沒想到你是這樣的 JDBC)。在之前我們可以通過 Class.forName來進(jìn)行Driver 具體實現(xiàn)類的加載。從JDK1.6開始,官方提供了一個名為 「SPI」 的機制,來更方便快捷的進(jìn)行對應(yīng)實現(xiàn)類的加載,不需要我們關(guān)心。我們所需要做的,只需要將包含實現(xiàn)類的 JAR 文件放到 classpath中即可。

正好最近讀了一些Dubbo的源碼,其中有 Dubbo 的不同于JDK的另一種 SPI實現(xiàn)。所以這篇我們來看 Dubbo 的 「SPI」實現(xiàn)以及與 JDK 實現(xiàn)的區(qū)別。

首先,什么是 SPI 呢?

SPI(Service Provider Interfaces), 可以理解成一個交給第三方實現(xiàn)的API。JDK文檔這樣描述

A service is a well-known set of interfaces and (usually abstract) classes. A service provider is a specific implementation of a service.

在Java 中使用到SPI的這些地方:

  • JDBC
  • JNDI
  • Java XML Processing API
  • Locael
  • NIO Channel Provider
  • ……

通過這種SPI 的實現(xiàn)形式,我們的應(yīng)用仿佛有了可插拔的能力。

我們之前的文章Tomcat 中 的可插拔以及 SCI 的實現(xiàn)原理 里,也分析了容器中是如何做到可插拔的。

JDK中的SPI 是怎樣實現(xiàn)的呢?

在JDK中包含一個SPI最核心的類:ServiceLoader,在需要加載Provider類的時候,我們所要做的是:

 
 
 
 
  1. ServiceLoader.load(Provider.class);

在JDK中規(guī)范了 Service Provider的路徑,所有 Provider必須在JAR文件的META-INF/services目錄下包含一個文件,文件名就是我們要實現(xiàn)的Service的名稱全路徑。比如我們熟悉的JDBC 的MySQL實現(xiàn), 在mysql-connector中,就有這樣一個文件

META-INF/services/java.sql.Driver

這些provider是什么時候加載的呢?

由于Provider 的加載和初始化是Lazy的實現(xiàn),所以需要的時候,可以遍歷Provider 的 Iterator,按需要加載,已經(jīng)加載的會存放到緩存中。

但有些實現(xiàn)不想Lazy,就直接在 ServiceLoader 的load執(zhí)行之后直接把所有的實現(xiàn)都加載和初始化了,比如這次說的JDBC,所以這里在Tomcat里有個處理內(nèi)存泄漏的,可以查看之前的文章(Tomcat與內(nèi)存泄露處理)

繼續(xù)說回具體的加載時機。我們一般在Spring 的配置中會增加一個datasource,這個數(shù)據(jù)源一般會在啟動時做為一個Bean被初始化,此時數(shù)據(jù)源中配置的driver會被設(shè)置。

這些內(nèi)容傳入Bean中,會調(diào)用DriverManager的初始化

 
 
 
 
  1. static {
  2.    loadInitialDrivers();
  3.     println("JDBC DriverManager initialized");
  4. }
  5. loadInitialDrivers 執(zhí)行的的時候,除了ServiceLoader.load外,還進(jìn)行了初始化
  6. ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
  7. Iterator driversIterator = loadedDrivers.iterator();
  8. try{
  9.     while(driversIterator.hasNext()) {
  10.         driversIterator.next();
  11.     }
  12. } catch(Throwable t) {
  13. // Do nothing
  14. }
  15. return null;

我們再來看 Dubbo 的SPI實現(xiàn)方式。如果你能看下 Dubbo 的源碼就會發(fā)現(xiàn),實現(xiàn)時并沒有使用 JDK 的SPI,而是自已設(shè)計了一種。

我們以Main class啟動來看看具體的實現(xiàn)。

我們從使用的入口處來看,***步傳入一個接口, 然后再傳入期待的實現(xiàn)的名稱

 
 
 
 
  1. SpringContainer container = (SpringContainer) ExtensionLoader.getExtensionLoader(Container.class).getExtension("spring");

這里傳入的是Container.class, 期待的實現(xiàn)是spring。

 
 
 
 
  1.  // synchronized in getExtensionClasses
  2.      private Map> loadExtensionClasses() {
  3.          final SPI defaultAnnotation = type.getAnnotation(SPI.class);
  4.          if (defaultAnnotation != null) {
  5.              String value = defaultAnnotation.value();
  6.              if ((valuevalue = value.trim()).length() > 0) {
  7.                  String[] names = NAME_SEPARATOR.split(value);
  8.                  if (names.length > 1) {
  9.                      throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
  10.                             + ": " + Arrays.toString(names));
  11.                 }
  12.                 if (names.length == 1) cachedDefaultName = names[0];
  13.             }
  14.         }
  15.         Map> extensionClasses = new HashMap>();
  16.         loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
  17.         loadDirectory(extensionClasses, DUBBO_DIRECTORY);
  18.         loadDirectory(extensionClasses, SERVICES_DIRECTORY);
  19.         return extensionClasses;
  20.     }

共從三個地方加載擴展的class

  • DUBBO_INTERNAL_DIRECTORY META-INF/dubbo/internal/
  • DUBBO_DIRECTORY META-INF/dubbo/
  • SERVICES_DIRECTORY META-INF/services/

 
 
 
 
  1.  private void loadDirectory(Map> extensionClasses, String dir) {
  2.          String fileName = dir + type.getName();
  3.          try {
  4.              Enumeration urls;
  5.              ClassLoader classLoader = findClassLoader();
  6.              if (classLoader != null) {
  7.                  urls = classLoader.getResources(fileName);
  8.              } else {
  9.                  urls = ClassLoader.getSystemResources(fileName);
  10.             }
  11.             if (urls != null) {
  12.                 while (urls.hasMoreElements()) {
  13.                     java.net.URL resourceURL = urls.nextElement();  
  14.                     loadResource(extensionClasses, classLoader, resourceURL);
  15.                 }
  16.             }
  17.         } catch (Throwable t) {
  18.             logger.error("Exception when load extension class(interface: " +
  19.                     type + ", description file: " + fileName + ").", t);
  20.         }
  21.     }

這里通過classLoader,尋找符合傳入的特定名稱的文件,java.net.URL resourceURL = urls.nextElement();

此時會得到一個包含該文件的URLPath, 再通過loadResource,將資源加載

此時得到的文件內(nèi)容是

 
 
 
 
  1. spring=com.alibaba.dubbo.container.spring.SpringContainer

再進(jìn)一步,將等號后面的class加載,即可完成。

loadClass時,并不是直接通過類似Class.forName等形式加載,而是下面這個樣子:

 
 
 
 
  1. private void loadClass(Map> extensionClasses, java.net.URL resourceURL, Class clazz, String name) throws NoSuchMethodException {
  2.          if (!type.isAssignableFrom(clazz)) {
  3.              throw new IllegalStateException("Error when load extension class(interface: " +
  4.                      type + ", class line: " + clazz.getName() + "), class "
  5.                      + clazz.getName() + "is not subtype of interface.");
  6.          }
  7.          if (clazz.isAnnotationPresent(Adaptive.class)) {
  8.              if (cachedAdaptiveClass == null) {
  9.                  cachedAdaptiveClass = clazz;
  10.             } else if (!cachedAdaptiveClass.equals(clazz)) {
  11.                 throw new IllegalStateException("More than 1 adaptive class found: "
  12.                         + cachedAdaptiveClass.getClass().getName()
  13.                         + ", " + clazz.getClass().getName());
  14.             }
  15.         } else if (isWrapperClass(clazz)) {
  16.             Set> wrappers = cachedWrapperClasses;
  17.             if (wrappers == null) {
  18.                 cachedWrapperClasses = new ConcurrentHashSet>();
  19.                 wrappers = cachedWrapperClasses;
  20.             }
  21.             wrappers.add(clazz);
  22.         } else {
  23.             clazz.getConstructor();
  24.             if (name == null || name.length() == 0) {
  25.                 name = findAnnotationName(clazz);
  26.                 if (name.length() == 0) {
  27.                     throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
  28.                 }
  29.             }
  30.             String[] names = NAME_SEPARATOR.split(name);
  31.             if (names != null && names.length > 0) {
  32.                 Activate activate = clazz.getAnnotation(Activate.class);
  33.                 if (activate != null) {
  34.                     cachedActivates.put(names[0], activate);
  35.                 }
  36.                 for (String n : names) {
  37.                     if (!cachedNames.containsKey(clazz)) {
  38.                         cachedNames.put(clazz, n);
  39.                     }
  40.                     Class c = extensionClasses.get(n);
  41.                     if (c == null) {
  42.                         extensionClasses.put(n, clazz);
  43.                     } else if (c != clazz) {
  44.                         throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
  45.                     }
  46.                 }
  47.             }
  48.         }
  49.     }

加載之后,需要對class進(jìn)行初始化,此時直接newInstance一個,再通過反射注入的方式將對應(yīng)的屬性設(shè)置進(jìn)去。

 
 
 
 
  1. private T createExtension(String name) {
  2.          Class clazz = getExtensionClasses().get(name);
  3.          if (clazz == null) {
  4.              throw findException(name);
  5.          }
  6.          try {
  7.              T instance = (T) EXTENSION_INSTANCES.get(clazz);
  8.              if (instance == null) {
  9.                  EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
  10.                 instance = (T) EXTENSION_INSTANCES.get(clazz);
  11.             }
  12.             injectExtension(instance);
  13.             Set> wrapperClasses = cachedWrapperClasses;
  14.             if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
  15.                 for (Class wrapperClass : wrapperClasses) {
  16.                     instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
  17.                 }
  18.             }
  19.             return instance;
  20.         } catch (Throwable t) {
  21.             throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
  22.                     type + ")  could not be instantiated: " + t.getMessage(), t);
  23.         }
  24.     }
 
 
 
 
  1. private T injectExtension(T instance) {
  2.         try {
  3.             if (objectFactory != null) {
  4.                 for (Method method : instance.getClass().getMethods()) {
  5.                     if (method.getName().startsWith("set")
  6.                             && method.getParameterTypes().length == 1
  7.                             && Modifier.isPublic(method.getModifiers())) {
  8.                         Class pt = method.getParameterTypes()[0];
  9.                         try {
  10.                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
  11.                            Object object = objectFactory.getExtension(pt, property);
  12.                            if (object != null) {
  13.                                method.invoke(instance, object);
  14.                            }
  15.                        } catch (Exception e) {
  16.                            logger.error("fail to inject via method " + method.getName()
  17.                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
  18.                        }
  19.                    }
  20.                }
  21.            }
  22.        } catch (Exception e) {
  23.            logger.error(e.getMessage(), e);
  24.        }
  25.        return instance;
  26.    }

通過上面的描述我們看到,JDK 與 Dubbo的 SPI 實現(xiàn)上,雖然都是從JAR中加載對應(yīng)的擴展,但還是有些明顯的區(qū)別,比如:Dubbo 支持更多的加載路徑,同時,并不是通過Iterator的形式,而是直接通過名稱來定位具體的Provider,按需要加載,效率更高,同時支持Provider以類似IOC的形式提供等等。

【本文為專欄作者“侯樹成”的原創(chuàng)稿件,轉(zhuǎn)載請通過作者微信公眾號『Tomcat那些事兒』獲取授權(quán)】


分享標(biāo)題:Dubbo的SPI實現(xiàn)以及與JDK實現(xiàn)的區(qū)別
網(wǎng)站地址:http://m.5511xx.com/article/ccdcopd.html