成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

詳解Java中的SPI技術(shù)以及在架構(gòu)設(shè)計(jì)的運(yùn)用

開發(fā) 前端
今天我們首先學(xué)習(xí)了 SPI 的定義,然后基于 JDK 中數(shù)據(jù)庫驅(qū)動的例子,重點(diǎn)講解了如何基于 SPI 來實(shí)現(xiàn)數(shù)據(jù)庫驅(qū)動的擴(kuò)展性。JDK 對數(shù)據(jù)庫驅(qū)動進(jìn)行了抽象,提供了抽象的 Driver 和 Connection 接口,這些接口就是 SPI 接口。

衡量一個架構(gòu)設(shè)計(jì)的好壞,其中一個標(biāo)準(zhǔn)就是看這個架構(gòu)是否具有可擴(kuò)展性,架構(gòu)設(shè)計(jì)中有很多常用的實(shí)現(xiàn)擴(kuò)展性的技術(shù),這次我們就來探討一下比較常見的 SPI 技術(shù)。

我們首先了解一下什么是 SPI,然后講一講 JDK 是如何基于 SPI 機(jī)制來獲取到具體的數(shù)據(jù)庫驅(qū)動實(shí)現(xiàn)的。接下來,我們分析 JDK SPI 機(jī)制的不足之處。最后,概要講解一下 Apache Dubbo 對 SPI 進(jìn)行了哪些改進(jìn),以及 Apache Dubbo 是如何基于增強(qiáng) SPI 實(shí)現(xiàn) Dubbo 框架的可擴(kuò)展性的。

服務(wù)提供者接口(Service provider interface,SPI),是指被第三方實(shí)現(xiàn)或者擴(kuò)展的接口,它可以用來實(shí)現(xiàn)框架的擴(kuò)展性和實(shí)現(xiàn)組件的可替換性。這里服務(wù)提供者接口中的服務(wù)是指一組接口和抽象類,服務(wù)提供者基于服務(wù)提供者接口來實(shí)現(xiàn)具體的服務(wù)。

JDK 中的 SPI 機(jī)制

了解了 SPI 的基本定義,我們接下來看一下 SPI 是如何在 JDK 中使用的。在 Java 開發(fā)中,我們經(jīng)常使用下面這段代碼來獲取一個數(shù)據(jù)庫連接。

DriverManager.getConnection("a database url of the form jdbc:subprotocol:subnam");

比如獲取 MySQL 數(shù)據(jù)庫連接,我們可以用如下代碼來操作:

DriverManager.getConnection("jdbc:mysql://localhost:3306/testDb?useUnicode=true&characterEncoding=UTF-8");

或者要獲取 Oracle 數(shù)據(jù)庫連接,對應(yīng)代碼如下所示:

DriverManager.getConnection("jdbc:oracle:thin:@localhost:3306:testDb");

獲取完數(shù)據(jù)庫連接后,我們該怎么用呢?

基于 MySQL 數(shù)據(jù)庫的示例,下面這段代碼就展示了如何基于連接做數(shù)據(jù)庫表的操作:

public static void main(String[] argc) throws SQLException {
  Connection con = null;
  try {
    //1. 獲取 MySQL 數(shù)據(jù)庫連接
    con = DriverManager.getConnection(
        "jdbc:mysql://localhost:3306/testDb?useUnicode=true&characterEncoding=UTF-8");
    //2. 執(zhí)行 SQL 語句
    Statement stmt = con.createStatement();
    ResultSet rs = stmt.executeQuery("SELECT * FROM table");
    //3. 處理結(jié)果集數(shù)據(jù)
    while (rs.next()) {
      String name = rs.getString("name");
      String desc = rs.getString("desc");
      System.out.println(name + ", " + desc);
    }
  } catch (SQLException e) {
    e.printStackTrace();
  } finally {
    //4. 關(guān)閉連接
    if (con != null) {
      con.close();
    }
  }

我們先獲取到 MySQL 數(shù)據(jù)庫的一個連接,然后基于連接執(zhí)行查詢操作,接著處理查詢操作返回的數(shù)據(jù)集,處理完畢后關(guān)閉連接。

從上面示例可知,DriverManager.getConnection 方法根據(jù)傳遞的 database url 不同,可以獲取不同數(shù)據(jù)庫的連接,也就是說 DriverManager.getConnection 方法是與具體的數(shù)據(jù)庫驅(qū)動實(shí)現(xiàn)無關(guān)的。這是一個很好的設(shè)計(jì),那么它是如何實(shí)現(xiàn)的呢?

首先,我們來剖析下 DriverManager.getConnection 的實(shí)現(xiàn)機(jī)制,我們列出來 DriverManager.getConnection 相關(guān)的類圖模型:

java.sql.Driver 文件的內(nèi)容如下圖:

圖片圖片

Oracle 驅(qū)動包中 META-INF/services/java.sql.Driver 文件的內(nèi)容如下所示:

圖片圖片

從 META-INF/services/java.sql.Driver 文件找到具體驅(qū)動的實(shí)現(xiàn)類的名稱后,會調(diào)用 ServiceLoader 內(nèi)的 nextService 方法,使用 Class.forName(“驅(qū)動實(shí)現(xiàn)類名稱”…)來創(chuàng)建這個驅(qū)動的 Class 對象,然后通過 Class 對象的 newInstance() 方法創(chuàng)建一個驅(qū)動實(shí)現(xiàn)類的實(shí)例對象。

private S nextService() {
    ...
    // 創(chuàng)建驅(qū)動實(shí)現(xiàn)類的 Class 對象
    Class<?> c = null;
    try {
        c = Class.forName(cn, false, loader);
    } catch (ClassNotFoundException x) {
        fail(service,
             "Provider " + cn + " not found");
    }
    ...
    // 基于驅(qū)動實(shí)現(xiàn)類的 Class 對象創(chuàng)建一個實(shí)例對象
    try {
        S p = service.cast(c.newInstance());
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
      ...
    }
}

另外,我們會發(fā)現(xiàn)具體的驅(qū)動實(shí)現(xiàn)類,比如 MySQL 驅(qū)動的 Driver 類內(nèi),存在一個 static 的代碼塊。

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
  public Driver() throws SQLException {
  }
  static {
    // 注冊 MySQL 驅(qū)動到 DriverManager
    try {
      DriverManager.registerDriver(new Driver());
    } catch (SQLException var1) {
      throw new RuntimeException("Can't register driver!");
    }
  }
}

這個 static 代碼塊會在創(chuàng)建 Driver 的實(shí)例對象時被觸發(fā)執(zhí)行,而上面 ServiceLoader 類的 nextService 方法內(nèi)就創(chuàng)建了 Driver 的實(shí)例,所以觸發(fā)了 Driver 類的 static 代碼塊執(zhí)行,也就是把 Driver 類注冊到了 DriverManager 中的 registeredDrivers 列表里面。

到這里,我們講解了 DriverManager.getConnection 內(nèi)的一部分邏輯,也就是 loadInitialDrivers 方法的邏輯。它的內(nèi)部使用 ServiceLoader 掃描 classpath 下所有的 jar 包,并找到實(shí)現(xiàn) Driver 接口的驅(qū)動包,然后注冊驅(qū)動實(shí)現(xiàn)類到 DriverManager。

上面我們講解了如何注冊驅(qū)動到 DriverManager,下面我們繼續(xù)看當(dāng) DriverManager.getConnection 獲取數(shù)據(jù)庫連接時,如何使用驅(qū)動來具體獲取數(shù)據(jù)庫連接的:

private static Connection getConnection(
    String url, java.util.Properties info, Class<?> caller) throws SQLException{}
       ...
       // 遍歷 registeredDrivers
        for(DriverInfo aDriver : registeredDrivers) {
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    // 從驅(qū)動獲取連接
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }
            }
          ...
        }
       ...
    }
}

由上面代碼可知,getConnection 方法內(nèi)會遍歷 registeredDrivers 中的驅(qū)動實(shí)現(xiàn)類,然后調(diào)用驅(qū)動實(shí)現(xiàn)類的 connect 方法,每個驅(qū)動實(shí)現(xiàn)類的 connect 方法根據(jù) URL 來判斷當(dāng)前請求的 URL 是否需要自己處理,如果不需要就返回 null,否則返回具體的連接對象。

總的來說,JDK 對數(shù)據(jù)庫驅(qū)動進(jìn)行了抽象,提供了 SPI 接口 Driver 和 Connection。然后,驅(qū)動開發(fā)者就可以實(shí)現(xiàn)這個 SPI 接口,來提供具體數(shù)據(jù)庫的驅(qū)動實(shí)現(xiàn)包。驅(qū)動開發(fā)者提供的驅(qū)動包里面需要包含 META-INF/services/java.sql.Driver 文件,并且文件內(nèi)要寫入驅(qū)動實(shí)現(xiàn)類的類名。

JDK 提供的 ServiceLoader 機(jī)制會掃描 classpath 下的所有 jar 包,并且找到含有 META-INF/services/java.sql.Driver 文件的 jar,判定它為數(shù)據(jù)庫驅(qū)動包,然后 ServiceLoader 會根據(jù)實(shí)現(xiàn)類的名稱實(shí)例化這個驅(qū)動實(shí)現(xiàn)類,并注冊驅(qū)動實(shí)現(xiàn)類到 DriverManager 內(nèi)。當(dāng)我們調(diào)用 DriverManager 的 getConnection 方法時,就可以獲取到具體的驅(qū)動實(shí)現(xiàn)類并獲取數(shù)據(jù)庫連接了。

請注意,在 JDK 的 SPI 機(jī)制實(shí)現(xiàn)中,ServiceLoader 會把所有驅(qū)動實(shí)現(xiàn)包中的驅(qū)動實(shí)現(xiàn)類都實(shí)例化(創(chuàng)建一個對應(yīng)的實(shí)例對象)。如果某些驅(qū)動實(shí)現(xiàn)類初始化很耗時,實(shí)例化會很浪費(fèi)資源,并且會降低應(yīng)用啟動速度。

Dubbo 中的 SPI 機(jī)制

Apache Dubbo 是一款微服務(wù)框架,為大規(guī)模微服務(wù)實(shí)踐提供高性能 RPC 通信、流量治理、可觀測性等解決方案。

Dubbo 的 SPI 實(shí)現(xiàn)借鑒了 JDK 的 SPI 思想,但是進(jìn)行了一些優(yōu)化改進(jìn),解決了 JDK SPI 的以下問題:

  1. JDK SPI 會一次性實(shí)例化所有實(shí)現(xiàn)類,有的擴(kuò)展實(shí)現(xiàn)類初始化很耗時,但實(shí)際又沒用,還會拖慢啟動速度;
  2. JDK SPI 在實(shí)例化擴(kuò)展實(shí)現(xiàn)類失敗時,不會友好地通知用戶具體異常。

Dubbo SPI 增加了對擴(kuò)展實(shí)現(xiàn)類的 IoC 和 AOP 的支持,一個擴(kuò)展實(shí)現(xiàn)類可以直接注入其它擴(kuò)展實(shí)現(xiàn)類,也可以使用 Wrapper 類對擴(kuò)展實(shí)現(xiàn)類進(jìn)行功能增強(qiáng)。

Dubbo 框架的實(shí)現(xiàn)采用了分層架構(gòu)思想,架構(gòu)中的每層都是一個獨(dú)立模塊,上層依賴下層提供的功能,下層對上層提供服務(wù),下層的改變對上層不可見。

圖片圖片

在這個架構(gòu)圖中,從上往下看,除去 Service 和 Config 層是 API 層外,剩下的從 Proxy 層到 Serialize 層都是 SPI 層,這意味著從 Proxy 層到 Serialize 層每層都是可擴(kuò)展的、可被替換的。

比如,Cluster 層默認(rèn)提供了豐富的集群容錯策略,但是如果開發(fā)者有定制化需求,可以通過 Dubbo 提供的 SPI 擴(kuò)展接口 org.apache.dubbo.rpc.cluster.Cluster 提供個性化的集群容錯策略,其中 SPI 接口 org.apache.dubbo.rpc.cluster.Cluster 的定義如下:

@SPI("failover")
public interface Cluster {
  @Adaptive
  <T> Invoker<T> join(Directory<T> var1) throws RpcException;
}

我們通過 CustomCluster 類實(shí)現(xiàn)了 SPI 接口——Cluster,其中 CustomClusterInvoker 為具體的容錯策略的實(shí)現(xiàn)。

public class CustomCluster implements Cluster {
  @Override
  public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
    return new CustomClusterInvoker<>(directory);
  }
}

創(chuàng)建好 CustomCluster 類后,我們需要在 resources 目錄下創(chuàng)建一個名稱為 META-INF.dubbo 的文件夾,然后在它的下面創(chuàng)建一個名為 org.apache.dubbo.rpc.cluster.Cluster 的文件,文件內(nèi)容為:customCluster=org.apache.dubbo.demo.cluster.CustomCluster。

圖片圖片

最后,我們在消費(fèi)端發(fā)起請求時,可以設(shè)置集群容錯策略。

// 0.創(chuàng)建服務(wù)引用對象實(shí)例
ReferenceConfig<GreetingService> referenceConfig = new ReferenceConfig<GreetingService>();
// 1.設(shè)置應(yīng)用程序信息
referenceConfig.setApplication(new ApplicationConfig("first-dubbo-consumer"));
// 2.設(shè)置服務(wù)注冊中心
referenceConfig.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
// 3.設(shè)置服務(wù)接口和超時時間
referenceConfig.setInterface(GreetingService.class);
// 4.設(shè)置集群容錯策略
referenceConfig.setCluster("customCluster");
// 5.設(shè)置服務(wù)分組與版本
referenceConfig.setVersion("1.0.0");
referenceConfig.setGroup("dubbo");
// 6.引用服務(wù)
greetingService = referenceConfig.get();

代碼 4 就是我們設(shè)置的集群容錯策略——customCluster。你可能會問,Dubbo 如何根據(jù)集群容錯策略的名稱——customCluster 找到具體的容錯策略實(shí)現(xiàn)類呢?其實(shí)就是通過 Dubbo 的增強(qiáng) SPI 機(jī)制來實(shí)現(xiàn)的,這個機(jī)制和 JDK SPI 機(jī)制差不多。

總結(jié)

圖片圖片

今天我們首先學(xué)習(xí)了 SPI 的定義,然后基于 JDK 中數(shù)據(jù)庫驅(qū)動的例子,重點(diǎn)講解了如何基于 SPI 來實(shí)現(xiàn)數(shù)據(jù)庫驅(qū)動的擴(kuò)展性。JDK 對數(shù)據(jù)庫驅(qū)動進(jìn)行了抽象,提供了抽象的 Driver 和 Connection 接口,這些接口就是 SPI 接口。

具體的驅(qū)動包實(shí)現(xiàn)者可以實(shí)現(xiàn)這些 SPI 接口來實(shí)現(xiàn)具體數(shù)據(jù)庫驅(qū)動。JDK 通過使用 ServiceLoader 機(jī)制來掃描驅(qū)動包的實(shí)現(xiàn)類,并注冊這些驅(qū)動到 DriverManager,所以我們可以通過 DriverManager.getConnection 方法獲取數(shù)據(jù)庫連接。

接下來,我們了解了 JDK SPI 的不足,概要介紹了 Dubbo 中增強(qiáng)的 SPI 的特點(diǎn)以及 Dubbo 如何基于 SPI 實(shí)現(xiàn)可擴(kuò)展性。最后,我們基于 Dubbo 的集群容錯策略擴(kuò)展接口,講解了 Dubbo 中如何來實(shí)現(xiàn)擴(kuò)展。

責(zé)任編輯:武曉燕 來源: 程序猿技術(shù)充電站
相關(guān)推薦

2017-05-17 14:51:31

DNS架構(gòu)負(fù)載均衡

2016-12-19 11:33:26

2009-06-12 16:07:05

演進(jìn)式架構(gòu)設(shè)計(jì)敏捷開發(fā)

2009-07-06 10:36:41

敏捷開發(fā)

2012-07-02 14:47:57

架構(gòu)敏捷開發(fā)

2021-07-21 16:30:38

iOSAPP架構(gòu)

2024-09-19 08:46:46

SPIAPI接口

2023-03-28 08:29:52

2011-04-08 17:03:19

Java架構(gòu)

2025-04-15 04:00:00

2022-05-27 11:27:31

技術(shù)架構(gòu)ROI

2010-01-15 10:15:34

分布式交換技術(shù)

2022-04-11 09:15:00

前端開發(fā)技術(shù)

2022-06-28 08:02:44

SPISpringJava

2017-08-17 09:49:06

云存儲技術(shù)運(yùn)用

2023-05-12 08:06:46

Kubernetes多云架構(gòu)

2025-06-27 09:24:38

MCP服務(wù)器系統(tǒng)

2025-03-13 13:00:00

架構(gòu)DNSIP

2025-04-28 09:00:00

DNS架構(gòu)網(wǎng)絡(luò)

2025-05-19 08:05:00

數(shù)據(jù)庫MVCCMySQL
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 色五月激情五月 | 九九精品网 | 国产精品美女www | 羞羞视频在线观看 | 国产视频中文字幕在线观看 | 国产成人精品a视频一区www | av黄色在线| 95国产精品 | 欧美日韩国产一区二区三区 | 亚洲精品久久久一区二区三区 | 成人精品一区二区三区中文字幕 | 日韩成人在线一区 | 毛色毛片免费看 | 成人欧美一区二区三区在线观看 | 天天天操天天天干 | 伊人精品国产 | 精品久久国产 | 日韩一区二区在线视频 | 岛国毛片 | 日韩欧美专区 | 91婷婷韩国欧美一区二区 | 先锋影音资源网站 | 国产精品亚洲成在人线 | 一区二区三区在线 | 九九精品网 | 日韩免费av| 欧美一级视频免费看 | 五月精品视频 | 蜜桃视频一区二区三区 | 国产精品久久久久国产a级 欧美日本韩国一区二区 | 国产视频在线一区二区 | 国产综合第一页 | 免费在线观看成年人视频 | 国产精品视频一二三区 | 超碰97免费 | 日韩欧美在线观看 | 午夜男人天堂 | 国产资源在线视频 | 在线视频中文字幕 | 久久久国产精品网站 | 午夜视频在线视频 |