Dubbo的SPI實(shí)現(xiàn)以及與JDK實(shí)現(xiàn)的區(qū)別
在 Java 里, 為了規(guī)范開(kāi)發(fā),制定了大量的「規(guī)范」與「標(biāo)準(zhǔn)」,這些上層的內(nèi)容,大多是以接口的形式提供出來(lái)。那這些接口最終實(shí)現(xiàn)是誰(shuí)呢,在哪里呢?
規(guī)范并不關(guān)心這個(gè)。
所謂規(guī)范,是指定了一系列內(nèi)容,來(lái)指導(dǎo)我們的開(kāi)發(fā)實(shí)現(xiàn)。比如 Servlet規(guī)范對(duì)于 Servlet 的行為做了說(shuō)明,具體實(shí)現(xiàn)時(shí),可以是 Tomcat,可以是Jetty 等等。
再比如 Java 的 JDBC 規(guī)范,規(guī)定了 Driver 提供者需要實(shí)現(xiàn)的內(nèi)容,但具體是 Oracle,或者M(jìn)ySQL 都可以支持。關(guān)于JDBC 可以看之前一篇文章(沒(méi)想到你是這樣的 JDBC)。在之前我們可以通過(guò) Class.forName來(lái)進(jìn)行Driver 具體實(shí)現(xiàn)類的加載。從JDK1.6開(kāi)始,官方提供了一個(gè)名為 「SPI」 的機(jī)制,來(lái)更方便快捷的進(jìn)行對(duì)應(yīng)實(shí)現(xiàn)類的加載,不需要我們關(guān)心。我們所需要做的,只需要將包含實(shí)現(xiàn)類的 JAR 文件放到 classpath中即可。
正好最近讀了一些Dubbo的源碼,其中有 Dubbo 的不同于JDK的另一種 SPI實(shí)現(xiàn)。所以這篇我們來(lái)看 Dubbo 的 「SPI」實(shí)現(xiàn)以及與 JDK 實(shí)現(xiàn)的區(qū)別。
首先,什么是 SPI 呢?
SPI(Service Provider Interfaces), 可以理解成一個(gè)交給第三方實(shí)現(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
 - ……
 
通過(guò)這種SPI 的實(shí)現(xiàn)形式,我們的應(yīng)用仿佛有了可插拔的能力。
我們之前的文章Tomcat 中 的可插拔以及 SCI 的實(shí)現(xiàn)原理 里,也分析了容器中是如何做到可插拔的。
JDK中的SPI 是怎樣實(shí)現(xiàn)的呢?
在JDK中包含一個(gè)SPI最核心的類:ServiceLoader,在需要加載Provider類的時(shí)候,我們所要做的是:
- ServiceLoader.load(Provider.class);
 
在JDK中規(guī)范了 Service Provider的路徑,所有 Provider必須在JAR文件的META-INF/services目錄下包含一個(gè)文件,文件名就是我們要實(shí)現(xiàn)的Service的名稱全路徑。比如我們熟悉的JDBC 的MySQL實(shí)現(xiàn), 在mysql-connector中,就有這樣一個(gè)文件
META-INF/services/java.sql.Driver
這些provider是什么時(shí)候加載的呢?
由于Provider 的加載和初始化是Lazy的實(shí)現(xiàn),所以需要的時(shí)候,可以遍歷Provider 的 Iterator,按需要加載,已經(jīng)加載的會(huì)存放到緩存中。
但有些實(shí)現(xiàn)不想Lazy,就直接在 ServiceLoader 的load執(zhí)行之后直接把所有的實(shí)現(xiàn)都加載和初始化了,比如這次說(shuō)的JDBC,所以這里在Tomcat里有個(gè)處理內(nèi)存泄漏的,可以查看之前的文章(Tomcat與內(nèi)存泄露處理)
繼續(xù)說(shuō)回具體的加載時(shí)機(jī)。我們一般在Spring 的配置中會(huì)增加一個(gè)datasource,這個(gè)數(shù)據(jù)源一般會(huì)在啟動(dòng)時(shí)做為一個(gè)Bean被初始化,此時(shí)數(shù)據(jù)源中配置的driver會(huì)被設(shè)置。
這些內(nèi)容傳入Bean中,會(huì)調(diào)用DriverManager的初始化
- static {
 - loadInitialDrivers();
 - println("JDBC DriverManager initialized");
 - }
 - loadInitialDrivers 執(zhí)行的的時(shí)候,除了ServiceLoader.load外,還進(jìn)行了初始化
 - ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
 - Iterator<Driver> driversIterator = loadedDrivers.iterator();
 - try{
 - while(driversIterator.hasNext()) {
 - driversIterator.next();
 - }
 - } catch(Throwable t) {
 - // Do nothing
 - }
 - return null;
 
我們?cè)賮?lái)看 Dubbo 的SPI實(shí)現(xiàn)方式。如果你能看下 Dubbo 的源碼就會(huì)發(fā)現(xiàn),實(shí)現(xiàn)時(shí)并沒(méi)有使用 JDK 的SPI,而是自已設(shè)計(jì)了一種。
我們以Main class啟動(dòng)來(lái)看看具體的實(shí)現(xiàn)。
我們從使用的入口處來(lái)看,***步傳入一個(gè)接口, 然后再傳入期待的實(shí)現(xiàn)的名稱
- SpringContainer container = (SpringContainer) ExtensionLoader.getExtensionLoader(Container.class).getExtension("spring");
 
這里傳入的是Container.class, 期待的實(shí)現(xiàn)是spring。
- // synchronized in getExtensionClasses
 - private Map<String, Class<?>> loadExtensionClasses() {
 - final SPI defaultAnnotation = type.getAnnotation(SPI.class);
 - if (defaultAnnotation != null) {
 - String value = defaultAnnotation.value();
 - if ((valuevalue = 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<?>>();
 - loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
 - loadDirectory(extensionClasses, DUBBO_DIRECTORY);
 - loadDirectory(extensionClasses, SERVICES_DIRECTORY);
 - return extensionClasses;
 - }
 
共從三個(gè)地方加載擴(kuò)展的class
- DUBBO_INTERNAL_DIRECTORY META-INF/dubbo/internal/
 - DUBBO_DIRECTORY META-INF/dubbo/
 - SERVICES_DIRECTORY META-INF/services/
 
- private void loadDirectory(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 resourceURL = urls.nextElement();
 - loadResource(extensionClasses, classLoader, resourceURL);
 - }
 - }
 - } catch (Throwable t) {
 - logger.error("Exception when load extension class(interface: " +
 - type + ", description file: " + fileName + ").", t);
 - }
 - }
 
這里通過(guò)classLoader,尋找符合傳入的特定名稱的文件,java.net.URL resourceURL = urls.nextElement();
此時(shí)會(huì)得到一個(gè)包含該文件的URLPath, 再通過(guò)loadResource,將資源加載
此時(shí)得到的文件內(nèi)容是
- spring=com.alibaba.dubbo.container.spring.SpringContainer
 
再進(jìn)一步,將等號(hào)后面的class加載,即可完成。
loadClass時(shí),并不是直接通過(guò)類似Class.forName等形式加載,而是下面這個(gè)樣子:
- private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
 - 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 if (isWrapperClass(clazz)) {
 - Set<Class<?>> wrappers = cachedWrapperClasses;
 - if (wrappers == null) {
 - cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
 - wrappers = cachedWrapperClasses;
 - }
 - wrappers.add(clazz);
 - } else {
 - clazz.getConstructor();
 - if (name == null || name.length() == 0) {
 - name = findAnnotationName(clazz);
 - if (name.length() == 0) {
 - throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
 - }
 - }
 - 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());
 - }
 - }
 - }
 - }
 - }
 
加載之后,需要對(duì)class進(jìn)行初始化,此時(shí)直接newInstance一個(gè),再通過(guò)反射注入的方式將對(duì)應(yīng)的屬性設(shè)置進(jìn)去。
- 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, clazz.newInstance());
 - instance = (T) EXTENSION_INSTANCES.get(clazz);
 - }
 - injectExtension(instance);
 - Set<Class<?>> wrapperClasses = cachedWrapperClasses;
 - if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
 - 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) {
 - 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;
 - }
 
通過(guò)上面的描述我們看到,JDK 與 Dubbo的 SPI 實(shí)現(xiàn)上,雖然都是從JAR中加載對(duì)應(yīng)的擴(kuò)展,但還是有些明顯的區(qū)別,比如:Dubbo 支持更多的加載路徑,同時(shí),并不是通過(guò)Iterator的形式,而是直接通過(guò)名稱來(lái)定位具體的Provider,按需要加載,效率更高,同時(shí)支持Provider以類似IOC的形式提供等等。
【本文為51CTO專欄作者“侯樹(shù)成”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過(guò)作者微信公眾號(hào)『Tomcat那些事兒』獲取授權(quán)】

















 
 
 








 
 
 
 