Dubbo SPI 、服務暴露、服務引入源碼解析

Dubbo的SPI

SPI什麼是SPI,SPI全稱爲Service Provider Interface,是一種服務發現機制,SPI的本質是將接口實現類的全限定名配置到文件中,並由服務器加載讀取配置文件,加載實現類,這樣可以在運行時,動態爲接口替換實現類,正因此特性,我們可以很容易的通過SPI機制爲我們的程序提供擴展功能,SPI機制在第三方框架中也有所應用,比如 Dubbo 就是通過 SPI 機制加載所有的組件。不過,Dubbo 並未使用 Java 原生的SPI 機制,而是對其進行了增強,使其能夠更好的滿足需求。在 Dubbo 中,SPI 是一個非常重的模塊。

1、Dubbo SPI

先看一下java原生的SPI,用到了反射機制,跟Spring的IOC裏的一樣,通過全限定類名,在程序運行階段,在內存中創建對象 newInstance。

創建一個接口類和兩個實現類

public interface User {
    public void service();
}


public class UserOneImpl implements User {
    public void service(){
        System.out.println("實現類一");
    }
}


public class UserTwoImpl implements User {
    public void service(){
        System.out.println("實現類二");
    }
}

在resources目錄下創建一個目錄,META-INF.services

在這個目錄下把,接口的全限定類名

com.jd.service.User  ,裏面配置內容是接口實現類的全限定類名

com.jd.service.impl.UserOneImpl
com.jd.service.impl.UserTwoImpl

然後編寫一個測試類

public class SpiTest {
    public static void main(String[] args) {
        ServiceLoader<User> load = ServiceLoader.load(User.class);
        //使用了反射機制,創建了對象。
        Iterator<User> iterator = load.iterator();
        while (iterator.hasNext()){
            User userImpl = iterator.next();
            userImpl.service();

        }
    }
}

控制檯輸出了 :實現類一   實現類二

原生SPI源碼分析原理,SerivceLoader進入這個類,可以看到成員變量寫好了路徑,然後調用load方法

private static final String PREFIX = "META-INF/services/";

進入load.iterator(),重寫了hasNext方法

 public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

進入hasNext方法

public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

進入hasNextService方法

   private boolean hasNextService() {
            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;
        }

 存到pending中,pending賦值給了nextName。

接下來回到iterator()方法中,繼續走next方法,進入lookupIterator中的next方法

  public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

然後進入nextService()方法中。把之前的nextName值付給了cn,cn通過反射,取消類名c,類c 被實例化newInstance(),賦值給了p,然後返回p。p就是實例化對象,然後我們用接口進行接收的。這個就是Java原生的SPI的一個源碼分析。

        private S nextService() {
            if (!hasNextService())
                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();          // This cannot happen
        }

這就是java的原生SPI。有一些缺點:

接口的所有實現類全部都需要加載並實例化

無法根據參數來獲取所對應的實現類

不能解決IOC、AOP的問題。基於以上問題,dubbo在原生SPI機制進行了增強,解決以上問題。

Dubbo的SPI機制,Dubbo SPI 的相關邏輯被封裝在了 ExtensionLoader 類中,通過 ExtensionLoader,我們可以加載指定的實現類。Dubbo SPI 所需的配置文件需放置在META-INF/dubbo 路徑下,配置內容如下,源碼

private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
test1=com.jd.service.impl.UserOneImpl
test2=com.jd.service.impl.UserTwoImpl

先引入座標依賴,

<!-- https://mvnrepository.com/artifact/com.alibaba/dubbo -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.5.3</version>
</dependency>

在接口上加一個註解@SPI

重新編寫一個測試類

public class DubboSpi {
    public static void main(String[] args) {
        ExtensionLoader<User> extensionLoader = ExtensionLoader.getExtensionLoader(User.class);
        User userImpl = extensionLoader.getExtension("test1");
        userImpl.service();
    }
}

控制檯輸出 ,實現類一,因爲只配了key值是test1

源碼分析,首先進入的是getExtensionLoader方法,通過 ExtensionLoader 的 getExtensionLoader 方法獲取一ExtensionLoader 實例,該方法方法先從緩存中獲取與拓展類對應的 ExtensionLoader,若緩存未命中,則創建一個新的實例

else {

            //從緩存中獲取,如果緩存中有直接返回loader,如果緩存中沒有,重新創建一個loader對象
            ExtensionLoader loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
            if(loader == null) {
                EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
                loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
            }

            return loader;

接下來看getExtension方法

public T getExtension(String name) {
		if (name == null || name.length() == 0)
		    throw new IllegalArgumentException("Extension name == null");
		if ("true".equals(name)) {
		    return getDefaultExtension();
		}
        //先從緩存中取,如果緩存中沒有就新建一個holder對象
		Holder<Object> holder = cachedInstances.get(name);
		if (holder == null) {
		    cachedInstances.putIfAbsent(name, new Holder<Object>());
		    holder = cachedInstances.get(name);
		}
        //然後調用holder中的get方法,創建instance對象,如果是新建holder對象,get出來的是空的,如果是從緩存中取的get出來的不是空的。
		Object instance = holder.get();
        //如果是空的,使用createExtension創建實例,instance代表的就是我們那個接口的實例對象。
		if (instance == null) {
		    synchronized (holder) {
	            instance = holder.get();
	            if (instance == null) {
	                instance = createExtension(name);
	                holder.set(instance);
	            }
	        }
		}
		return (T) instance;
	}

接下來進入createExtension方法中

 private T createExtension(String name) {
        //1、根據name值就是key值,通過getExtensionClasses()獲取所有的拓展類,如果等於null就報異常
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            //也是,從緩存中取拿,如果緩存沒有,就創建一個
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
            //2、通過反射創建拓展對象實例
                EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            //3、需要依賴注入,向實例中注入依賴
           //此處injectExtension方法依賴注入實現原理:Dubbo 首先會通過反射獲取到實例的所 
           //  有方法,然後再遍歷方法列表,檢測方法名是否具有 setter 方法特徵。若有,則通過 
           //ObjectFactory 獲取依賴對象,最後通過反射調用 setter 方法將依賴設置到目標對象 
           // 中。
            injectExtension(instance);
            
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && wrapperClasses.size() > 0) {
                //4、將拓展對象包裹在相應的 Wrapper 對象中循環創建 Wrapper 實例
                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);
        }
    }

 

Dubbo的服務暴露

 

Dubbo的服務引入

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章