SPI架構實作之旅一:背景介紹
SPI的全名為Service Provider Interface,簡單的總結下java spi機制的思想。我們系統裡抽象的各個子產品,往往有很多不同的實作方案,比如日志子產品的方案,xml解析子產品、jdbc子產品的方案等。面向的對象的設計裡,我們一般推薦子產品之間基于接口程式設計,子產品之間不對實作類進行寫死。一旦代碼裡涉及具體的實作類,就違反了可拔插的原則,如果需要替換一種實作,就需要修改代碼。為了實作在子產品裝配的時候能不在程式裡動态指明,這就需要一種服務發現機制。 java spi就是提供這樣的一個機制:為某個接口尋找服務實作的機制
1. 背景
上面摘抄了一下spi的概念,接着以個人的了解,簡單的談一下為什麼會用到SPI, 什麼場景下可以用到這個, 以及使用了SPI機制後有什麼優越性
什麼是SPI
雖然最開始就引用了spi的解釋,這裡淺談一下個人了解。
Service Provider Interface
以接口方式提供服務, 和API不同,spi的機制是定義一套标準規範的接口,實作交給其他人來做。
是以一個接口,可以有很多的實作,你完全可以根據自己的需要去選擇具體的實作方式,因為是面向接口的開發,是以你的業務代碼基本上就不用修改,就可以切到另一個實作了
什麼場景可以用
分别從架構層面和業務層面,給出一個我認為比較合适的場景
1. 日志輸出 SLF4j
SLF4j
SLF4j:大名鼎鼎的日志輸出接口,這個jar包裡面提供的都隻是接口方式,具體的實作需要自己去實作,當然比較常用的
logback
就是一個具體的實作包了, 在項目中使用
slf4j
的api進行日志的輸出, 通過簡單的配置,引入logback, 就可以使用logback來實作具體的日志輸出; 也可以換一個日志實作
commons-logging
,業務上不需要任何的改動,就可以用不同的實作來輸出日志
2. 業務場景
假設你現在有個使用者注冊成功後的歡迎使用者的業務,不同管道(微信,qq,微網誌等)注冊的,顯示的歡迎不同,對此有兩種不同的實作方式
- 如果每個不同的管道進來的,都有一個獨立的應用來響應 (因為絕大多數的業務都一樣,可能就歡迎詞不同,如果做到代碼最大程度的複用)
- 隻有一個應用,來處理所有的這些場景
可以怎麼用
結合上面的業務場景,來描述下可以怎麼用
1. 代碼複用
為了實作代碼最大程度的複用,那麼可以将不同的地方,抽象成一個SPI接口,在業務層通過接口來代替具體的實作類實作業務邏輯;
每個管道,都有個獨立的應用,那麼在微信管道,建立一個 WeixinSpiImpl來實作接口
在qq管道,實作 QQSpiImpl;那麼在具體的接口調用處,實際上就是執行的spi實作類方法
2. 業務場景的選擇區分
這個與上面不同,同一個服務接口,根據不同的業務場景,選擇不同的實作來執行;當然你是完全可以使用 if, else來實作這種場景,唯一的問題就是擴充比較麻煩;
這種場景下,我們希望的就是這個接口,能自動的根據業務場景,來選擇最合适的實作類來執行
簡單來講,就是 spi接口執行之前,其實需要有一個自動選擇比對的實作類的前置過程;
通常這種業務場景下,具體的spi實作會有多個,但是需要有一個選擇的政策
2. 小目标
在具體的實作之前,先定義一個小目标,我們想要實作一個什麼樣子的東西出來
通過上面的背景描述,我們的小目标也就很明确了,我們的實作至少需要滿足兩個場景
- 靜态選擇SPI實作, 即在選擇完成之後,所有對這個spi接口的引用都是确定由這個實作來承包
- 動态選擇SPI實作, 不到運作之時,你都不知道會是哪個spi實作來幹這件事
3. 技術儲備
java本身就提供了一套spi的支援方式: ServiceLoader
,我們後續的開發,也會在這個基礎之上進行
利用java的
ServiceLoader
找到服務接口的實作類,有一些約定,下面給出要求說明和一個測試case
一般實作流程
- 定義spi接口 :
IXxx
- 具體的實作類:
, AXxx
BXxx
- 在jar包的
目錄下建立一個檔案,命名為 spi接口的完整類名,内容為spi接口實作的完整類名,一個實作類占一行META-INF/services/
測試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();
}
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!");
}
}
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");
}
}
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
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();
}
}
}
public class HelloSpiTest {
@Test
public void testSPI() {
ServiceLoader<HelloInterface> serviceLoader = ServiceLoader.load(HelloInterface.class); for (HelloInterface hello: serviceLoader) {
hello.sayHello();
}
}
}
輸出如下:
image hello!
text hello
image hello!
text hello
測試類示範如下圖:

4. 設計思路
畫了一下結構圖,友善了解, 下面的核心是
SpiLoader
類, 負責加載spi接口的所有實作類, 初始化所有定義的選擇器, 傳回一個spi接口的實作類初始化使用者自定義的spi對象,然後使用者持有此對象調用spi接口中提供的方法即可
其他
- SPI架構實作之旅一:背景介紹
- SPI架構實作之旅二:整體設計
- SPI架構實作之旅三:實作說明
- SPI架構實作之旅四:使用測試