【dubbo系列】SPI機制源碼解析

​ SPI 全稱爲 Service Provider Interface,是一種服務發現機制。此機制在dubbo中大量使用。以至於dubbo框架的及其靈活。

​ dubbo SPI 源碼地址:http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html

1.java SPI源碼解析

1.1 SPI使用場景

​ dubbo官方中有java SPI的示例,但是初始並不是特別的理解。或者根本不理解這種設計的好處。直到看到mysql驅動包才恍然大悟。

​ java官方爸爸想連接數據庫,但是連接數據庫就要有數據庫驅動,但是流行的數據庫太多了,難道要一一實現麼?這樣太費事了。然後jdk就創建了個Driver接口,此接口裏面定義瞭如下方法:
在這裏插入圖片描述
​ 看不懂方法是幹啥的沒關係,反正不是普通開發人員來處理的。但是,如此就定義了標準,也就是說,如果想要java來連接數據庫,那就廠商或者想連接的開發人員自己去實現此接口就好。但是如果實現的多了java就不知道如何選擇了。比如,加了mysql的包,也加了oracle的包,或者自己也實現了這個接口,在多接口的時候,如何選擇就成了問題。這個時候SPI機制就有了作用了。

​ 既然這樣,如果要連接mysql數據庫的話, 那麼mysql驅動包裏,一定要實現此接口。
在這裏插入圖片描述
​ 如上發現,mysql包中確實是有實現Driver接口。內容如下。

​ com.mysql.cj.jdbc.Driver

1.2 java SPI源碼解析

1.2.1 示例

​ 假如有如下接口

public interface Robot {
    void sayHello();
}

接下來定義兩個實現類,分別爲 OptimusPrime 和 Bumblebee。

public class OptimusPrime implements Robot {
    
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Optimus Prime.");
    }
}

public class Bumblebee implements Robot {

    @Override
    public void sayHello() {
        System.out.println("Hello, I am Bumblebee.");
    }
}

接下來 META-INF/services 文件夾下創建一個文件,名稱爲 Robot 的全限定名 org.apache.spi.Robot。文件內容爲實現類的全限定的類名,如下:

org.apache.spi.OptimusPrime
org.apache.spi.Bumblebee

public class JavaSPITest {

    @Test
    public void sayHello() throws Exception {
        ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
        System.out.println("Java SPI");
        for(Robot service : services) {
            service.sayHello();
        }
    }
}

輸出:

Java SPI

Hello, I am Optimus Prime.

Hello, I am Bumblebee.

1.2.2解析

​ 原本以爲內容加載是在load方法中處理,但是找了一圈也沒找到個啥。到最後卻發現在for循環中處理的。可能這樣看感覺很蒙,很簡單的for循環能有多少內容呀?

    public static void main(String[] args) {
        ServiceLoader<Robot> services = ServiceLoader.load(Robot.class);
        Iterator var2 = services.iterator();

        while(var2.hasNext()) {
            Robot service = (Robot)var2.next();
            service.sayHello();
        }

    }

​ 代碼經過反編譯,卻變成了這樣。那一個迭代器,大家都知道的,能有啥內容呢?ServiceLoad對迭代器中的內容進行了重寫。在執行services.iterator();這段代碼的時候,是執行如下代碼。

​ 這裏就涉及到了Iterableiterator這兩個類的內容了,詳細看這篇博文,寫的挺詳細的https://www.cnblogs.com/litexy/p/9744241.html

   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方法和next方法就好了。

1.2.3hasNext方法解析

 public boolean hasNext() {
     		//訪問控制上下文 爲null
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }
private boolean hasNextService() {
    		// 如果有值,直接返回了,這個是在下面進行設置。
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    //PREFIX 是META-INF/services/ 這個值是固定死的
                    // service 是設置類的全名,包括路徑名
                    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;
        }

1.2.4 next方法解析

            public S next() {
                
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                //第一次走這裏
                return 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);
            }
        }
private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
    		// 獲取值,這個在hasNext的時候就設置好了。
            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
        }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章