1. 程式人生 > >Java拾遺(一)之SPI機制

Java拾遺(一)之SPI機制

例子:

        IOperation plus = new PlusOperationImpl();
        IOperation division = new DivisionOperationImpl();
        System.out.println(plus.operation(6, 3));//加法
        System.out.println(division.operation(6, 3));//除法

通常我們要定義一個四則運算介面IOperation,然後會寫他的實現類PlusOperationImpl,DivisionOperationImpl。

然後在各自的實現類中先實現介面,實現相應的方法。

但是,java設計來一波很sao的操作。

再看另外一個例子:

            Class.forName("com.mysql.jdbc.Driver");
    		String url = "jdbc:mysql://localhost:3306/test";
    		String username = "root";
    		String password = "123";
    		Connection conn = DriverManager.getConnection(url,username,password);

上面是jdbc獲取連線的程式碼,很平常。但是我們不好奇它是怎麼實現的嗎?

首先是第一行載入類,所以看看com.mysql.jdbc.Drver

package com.mysql.jdbc;
static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

他有一個靜態程式碼塊,我們都知道類在載入的時候是會執行靜態程式碼塊的,他new了一個Driver物件,術語叫註冊驅動,說人話就是把新建的物件放到記憶體中去了後面來使用。最終它會存到java.sql.DriverManager類中的CopyOnWriteArrayList<DriverInfo> registeredDrivers屬性裡。後面會迭代這個list。

第一步事情做完了,下面就是DriverManager.getConnection這個方法獲取連線了,

@CallerSensitive
    public static Connection getConnection(String url,
        String user, String password) throws SQLException {
        java.util.Properties info = new java.util.Properties();

        if (user != null) {
            info.put("user", user);
        }
        if (password != null) {
            info.put("password", password);
        }

        return (getConnection(url, info, Reflection.getCallerClass()));
    }

然後又呼叫getConnection方法:

private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        ......//

        for(DriverInfo aDriver : registeredDrivers) {
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }
            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }
......///
    }

去掉一些,只看關鍵部分 Connection con = aDriver.driver.connect(url, info);這個才是獲取連線的

然後你會發現,他丫的aDriver.driver是個介面,然鵝它就這麼呼叫了。真是會玩,介面也能調。

當然介面肯定不能直接拿來用,上面肯定藏著它的實現類,點進DriverInfo你會發現它它好像內部類,因為它所在檔案的類名叫java.sql.DriverManager,仔細看才發現他丫的公用了一個檔案才不是什麼內部類,也就是隻要是同一個包裡面就可以new了。

如果你在這行打個斷點,debug的時候會發現aDriver.driver不是介面了,已經被初始化了。

原理就是這個類,java.util.ServiceLoader。你要問我,我怎麼知道是在這個類裡面。祕訣就是debug,driver的初始化時在這個類DriverInfo裡面,而這個類就一個構造方法。咱就在這個構造方法打個斷點,果然執行了,然後你在一步步debug(真是很暈,你會N多個類)

最終的最終你會到達ServiceLoader它定義了一個變數

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

而且還有值了。

然後去mysql的jar包下面/META-INF/services/java.sql.Driver檔案裡面內容:

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

看到這,後面就不看程式碼了。

總結一下:

所謂的SPI機制,就是說把介面和實現分離(通常不在一個jar包裡)

也就是寫介面的人和寫實現類的人不是同一個。

然後介面的提供方會獲取對應的實現,呼叫對應的方法。而具體的實現還是在實現類裡面

一方提供規範(介面)和邏輯(具體呼叫哪些方法),另一方根據規範實現相應介面和方法。呼叫提供方給的靜態方法即可