1. 程式人生 > 實用技巧 >java中的spi程式設計

java中的spi程式設計

spi程式設計服務介面提供,我們首先看一個示例來理解java的spi程式設計

我們編寫了一個介面UploadCDN,同時為他提供了兩個實現類來提供提供服務,Test方法如下,同時在resources/META-INF/services下配置與與介面名稱相同的檔名稱的全路徑,裡面配置了兩個實現類的路徑

public class Test {
    public static void main(String[] args) {
        ServiceLoader<UploadCDN> serviceLoader=ServiceLoader.load(UploadCDN.class);
        for(UploadCDN u:serviceLoader){
            u.upload("測試spi");
        }
    }
}

輸出結果如下

重點就在serviceloader這個類,通過他的load方法,傳入介面的型別。我們看一下serviceloader類,下圖可知為什麼要在META-INF/services目錄下

serviceloader的load方法

 public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

呼叫了過載的方法,多傳入了一個ClassLoader屬性,後續用於載入我們配置的兩個實現類,經過一系列的呼叫我們看到在reload方法中建立了一個LazyIterator,這個LazyIterator是一個內部類實現了Iterator,也就是一個內部的迭代器。

 private class LazyIterator
        implements Iterator<S>
    {

        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        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;
        }

        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
        }

可以看到在這裡記載了我們配置在配置檔案中的兩個實現類並進行了初始化,所以我們在Test方法中能夠進行呼叫。

那麼spi程式設計有什麼作用呢,我們在學習資料庫的時候,會記得市面上會有各種各樣的資料庫,他們的語法不盡相同,因此java提供了一個介面,由資料庫廠商自己定義實現,我們需要那個資料庫就匯入相應的jar包即可,回憶一下jdbc連線資料庫的程式碼

    public static void main(String[] args) throws ClassNotFoundException, SQLException  {
        //1.載入驅動程式
        Class.forName("com.mysql.jdbc.Driver");
        //2.獲得資料庫連結
        Connection conn=DriverManager.getConnection(URL, USER, PASSWORD);
        //3.通過資料庫的連線操作資料庫,實現增刪改查(使用Statement類)
        Statement st=conn.createStatement();
        ResultSet rs=st.executeQuery("select * from user");
        //4.處理資料庫的返回結果(使用ResultSet類)
        while(rs.next()){
            System.out.println(rs.getString("user_name")+" "
                          +rs.getString("user_password"));
        }

        //關閉資源
        rs.close();
        st.close();
        conn.close();
}

我們可以看一下一個資料庫的jar

是不是和我們的測試很像呢,其實現在第一步classforname是不需要我們主動去做的原因就是spi程式設計,我們可以看看DriverManager類其中有一個靜態程式碼塊

static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}

可以看一下loadInitiaDrivers方法

是不是有些熟悉呢,在這個方法中使用了serviceloader去載入了META-INF下配置的com.mysql.jdbc.Driver類。