SPI框架實現之旅一:背景介紹

SPI框架實現之旅一:背景介紹

SPI的全名爲Service Provider Interface,簡單的總結下java spi機制的思想。咱們系統裏抽象的各個模塊,每每有不少不一樣的實現方案,好比日誌模塊的方案,xml解析模塊、jdbc模塊的方案等。面向的對象的設計裏,咱們通常推薦模塊之間基於接口編程,模塊之間不對實現類進行硬編碼。一旦代碼裏涉及具體的實現類,就違反了可拔插的原則,若是須要替換一種實現,就須要修改代碼。爲了實如今模塊裝配的時候能不在程序裏動態指明,這就須要一種服務發現機制。 java spi就是提供這樣的一個機制:爲某個接口尋找服務實現的機制java

1. 背景

上面摘抄了一下spi的概念,接着以我的的理解,簡單的談一下爲何會用到SPI, 什麼場景下能夠用到這個, 以及使用了SPI機制後有什麼優越性git

什麼是SPI

雖然最開始就引用了spi的解釋,這裏淺談一下我的理解。Service Provider Interface 以接口方式提供服務, 和API不一樣,spi的機制是定義一套標準規範的接口,實現交給其餘人來作。編程

因此一個接口,能夠有不少的實現,你徹底能夠根據本身的須要去選擇具體的實現方式,由於是面向接口的開發,因此你的業務代碼基本上就不用修改,就能夠切到另外一個實現了api

什麼場景能夠用

分別從框架層面和業務層面,給出一個我認爲比較合適的場景微信

1. 日誌輸出 SLF4j

SLF4j:大名鼎鼎的日誌輸出接口,這個jar包裏面提供的都只是接口方式,具體的實現須要本身去實現,固然比較經常使用的 logback 就是一個具體的實現包了, 在項目中使用 slf4j 的api進行日誌的輸出, 經過簡單的配置,引入logback, 就可使用logback來實現具體的日誌輸出; 也能夠換一個日誌實現 commons-logging,業務上不須要任何的改動,就能夠用不一樣的實現來輸出日誌框架

2. 業務場景

假設你如今有個用戶註冊成功後的歡迎用戶的業務,不一樣渠道(微信,qq,微博等)註冊的,顯示的歡迎不一樣,對此有兩種不一樣的實現方式ide

  • 若是每一個不一樣的渠道進來的,都有一個獨立的應用來響應 (由於絕大多數的業務都同樣,可能就歡迎詞不一樣,若是作到代碼最大程度的複用)
  • 只有一個應用,來處理全部的這些場景

能夠怎麼用

結合上面的業務場景,來描述下能夠怎麼用測試

1. 代碼複用

爲了實現代碼最大程度的複用,那麼能夠將不一樣的地方,抽象成一個SPI接口,在業務層經過接口來代替具體的實現類實現業務邏輯;ui

每一個渠道,都有個獨立的應用,那麼在微信渠道,建立一個 WeixinSpiImpl來實現接口編碼

在qq渠道,實現 QQSpiImpl;那麼在具體的接口調用處,實際上就是執行的spi實現類方法

2. 業務場景的選擇區分

這個與上面不一樣,同一個服務接口,根據不一樣的業務場景,選擇不一樣的實現來執行;固然你是徹底可使用 if, else來實現這種場景,惟一的問題就是擴展比較麻煩;

這種場景下,咱們但願的就是這個接口,能自動的根據業務場景,來選擇最合適的實現類來執行

簡單來說,就是 spi接口執行以前,其實須要有一個自動選擇匹配的實現類的前置過程;

一般這種業務場景下,具體的spi實現會有多個,可是須要有一個選擇的策略


2. 小目標

在具體的實現以前,先定義一個小目標,咱們想要實現一個什麼樣子的東西出來

經過上面的背景描述,咱們的小目標也就很明確了,咱們的實現至少須要知足兩個場景

  1. 靜態選擇SPI實現, 即在選擇完成以後,全部對這個spi接口的引用都是肯定由這個實現來承包
  2. 動態選擇SPI實現, 不到運行之時,你都不知道會是哪一個spi實現來幹這件事

3. 技術儲備

java自己就提供了一套spi的支持方式: ServiceLoader,咱們後續的開發,也會在這個基礎之上進行

利用java的 ServiceLoader 找到服務接口的實現類,有一些約定,下面給出要求說明和一個測試case

通常實現流程

  • 定義spi接口 : IXxx
  • 具體的實現類: AXxx, BXxx
  • 在jar包的META-INF/services/目錄下新建一個文件,命名爲 spi接口的完整類名,內容爲spi接口實現的完整類名,一個實現類佔一行

測試case以下

spi接口 com.hust.hui.quicksilver.commons.spi.HelloInterface

package com.hust.hui.quicksilver.commons.spi;

/**
 * Created by yihui on 2017/3/17.
 */
public interface HelloInterface {

    void sayHello();

}

spi接口的兩個實現類

com.hust.hui.quicksilver.commons.spi.impl.ImageHello.java

package com.hust.hui.quicksilver.commons.spi.impl;

import com.hust.hui.quicksilver.commons.spi.HelloInterface;

/**
 * Created by yihui on 2017/3/17.
 */
public class ImageHello implements HelloInterface {
    @Override
    public void sayHello() {
        System.out.println("image hello!");
    }
}

com.hust.hui.quicksilver.commons.spi.impl.TextHello.java

package com.hust.hui.quicksilver.commons.spi.impl;

import com.hust.hui.quicksilver.commons.spi.HelloInterface;

/**
 * Created by yihui on 2017/3/17.
 */
public class TextHello implements HelloInterface {
    @Override
    public void sayHello() {
        System.out.println("text hello");
    }
}

配置文件 com.hust.hui.quicksilver.commons.spi.HelloInterface

com.hust.hui.quicksilver.commons.spi.impl.ImageHello
com.hust.hui.quicksilver.commons.spi.impl.TextHello

測試類

public class HelloSpiTest {

    @Test
    public void testSPI() {
        ServiceLoader<HelloInterface> serviceLoader = ServiceLoader.load(HelloInterface.class);

        for (HelloInterface hello: serviceLoader) {
            hello.sayHello();
        }
    }
}

輸出以下:

image hello!
text hello

測試類演示以下圖:

演示圖


4. 設計思路

畫了一下結構圖,方便理解, 下面的核心是 SpiLoader 類, 負責加載spi接口的全部實現類, 初始化全部定義的選擇器, 返回一個spi接口的實現類初始化用戶自定義的spi對象,而後用戶持有此對象調用spi接口中提供的方法便可

https://static.oschina.net/uploads/img/201705/26185143_ULnL.png


其餘

博客系列連接:

源碼地址:

https://git.oschina.net/liuyueyi/quicksilver/tree/master/silver-spi

相關文章
相關標籤/搜索