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

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

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

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

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

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

JDK 中的 SPI 機(jī)制

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

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

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

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

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

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

獲取完數(shù)據(jù)庫(kù)連接后,我們?cè)撛趺从媚兀?/p>

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

public static void main(String[] argc) throws SQLException {
  Connection con = null;
  try {
    //1. 獲取 MySQL 數(shù)據(jù)庫(kù)連接
    con = DriverManager.getConnection(
        "jdbc:mysql://localhost:3306/testDb?useUnicode=true&characterEncoding=UTF-8");
    //2. 執(zhí)行 SQL 語(yǔ)句
    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ù)庫(kù)的一個(gè)連接,然后基于連接執(zhí)行查詢操作,接著處理查詢操作返回的數(shù)據(jù)集,處理完畢后關(guān)閉連接。

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

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

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

圖片圖片

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

圖片圖片

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

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

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

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

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

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

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

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ū)動(dòng)獲取連接
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }
            }
          ...
        }
       ...
    }
}

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

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

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

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

Dubbo 中的 SPI 機(jī)制

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

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

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

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

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

圖片圖片

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

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

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

我們通過(guò) CustomCluster 類實(shí)現(xiàn)了 SPI 接口——Cluster,其中 CustomClusterInvoker 為具體的容錯(cuò)策略的實(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)建一個(gè)名稱為 META-INF.dubbo 的文件夾,然后在它的下面創(chuàng)建一個(gè)名為 org.apache.dubbo.rpc.cluster.Cluster 的文件,文件內(nèi)容為:customCluster=org.apache.dubbo.demo.cluster.CustomCluster。

圖片圖片

最后,我們?cè)谙M(fèi)端發(fā)起請(qǐng)求時(shí),可以設(shè)置集群容錯(cuò)策略。

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

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

總結(jié)

圖片圖片

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

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

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

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

2017-05-17 14:51:31

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

2016-12-19 11:33:26

2012-07-02 14:47:57

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

2009-06-12 16:07:05

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

2009-07-06 10:36:41

敏捷開發(fā)

2024-09-19 08:46:46

SPIAPI接口

2021-07-21 16:30:38

iOSAPP架構(gòu)

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

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

2023-05-12 08:06:46

Kubernetes多云架構(gòu)

2016-10-21 14:57:10

2025-05-19 08:05:00

數(shù)據(jù)庫(kù)MVCCMySQL

2025-03-13 13:00:00

架構(gòu)DNSIP

2025-04-28 09:00:00

DNS架構(gòu)網(wǎng)絡(luò)
點(diǎn)贊
收藏

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

主站蜘蛛池模板: 国产激情福利 | 日韩中文欧美 | 天天色影视综合 | www.成人.com | 国产成人精品综合 | 亚洲午夜精品一区二区三区他趣 | 激情五月综合 | 国产中文 | 欧美日韩精品区 | 性高湖久久久久久久久 | 高清国产一区二区 | www.天天干.com | 国产午夜一级 | 久久久久国产一区二区三区 | 成人午夜视频在线观看 | 国产精品久久久久久久久久久久 | 国产高清免费视频 | 日韩精品免费一区二区在线观看 | 中文字幕国产精品视频 | 在线看亚洲 | 欧美综合一区二区 | av一区二区三区四区 | 99精品国自产在线 | 欧美日韩在线观看一区 | 亚洲一区不卡 | 欧美国产日韩一区二区三区 | 中文字幕视频在线 | 亚洲精品久久视频 | 国产成人精品一区二三区在线观看 | 欧美日韩一区不卡 | 国产伦精品| 久色一区| 天天干天天干 | av在线三级 | 日中文字幕在线 | 亚洲一区二区三区桃乃木香奈 | www.久久| 久久国色 | 亚洲国产精品视频 | 亚洲性综合网 | 成人美女免费网站视频 |