Java SPI詳細的例子
- 2020 年 9 月 14 日
- 筆記
- oracle spi, spi
先翻一個來自於Baeldung的介紹:
為了更通俗易懂我就沒有直譯,如果有不嚴謹的地方請大神指教。
JavaSPI的定義
Java SPI defines four main components
SPI四個主要概念
1. Service
服務
A well-known set of programming interfaces and classes that provide access to some specific application functionality or feature.
服務是一組介面類,用來定義某些功能或特性的介面(常常是某個jar包的功能或特性),暴露給其他程式調用。
2. Service Provider Interface
服務提供者介面
An interface or abstract class that acts as a proxy or an endpoint to the service.
If the service is one interface, then it is the same as a service provider interface.
Service and SPI together are well-known in the Java Ecosystem as API.
一個介面或抽象類,用來代理一個服務端。
如果是一個介面服務,那麼這就和一個服務提供者介面一樣。
服務和SPI一起便是java生態中的API了。
還是來一段自己的理解吧。。。就是定義介面,用來被別人去實現
服務提供者介面 – 如果我們有一個MyService的介面,我們把介面打進jar包,留給別人去依賴並實現。那這個介面就是服務提供者的介面。我們簡單的理解成這個jar是服務介面提供者。
沒錯,我們就是服務提供者。我們制定規則,定義介面。接下來留個各個廠商去實現各個介面。
比如JDBC,Java開發者把鏈接資料庫的方法寫成介面。這個介面只要傳進去url name passwrod 就能鏈接DB了。這就是一個SPI
當然很重要一步的是要由每種不同的DB廠商去實現具體怎麼用這麼幾個簡單的參數去實現鏈接。廠商把實現打個jar包(比如:Oracle的ojdbc-xx.jar),開發者把他加到classpath中就可以鏈接資料庫了。
3. Service Provider
服務提供者
A specific implementation of the SPI. The Service Provider contains one or more concrete classes that implement or extends the service type.
A Service Provider is configured and identified through a provider configuration file which we put in the resource directory META-INF/services. The file name is the fully-qualified name of the SPI and his content is the fully-qualified name of the SPI implementation.
The Service Provider is installed in the form of extensions, a jar file which we place in the application classpath, the java extension classpath or user-defined classpath.
不需要翻譯了,簡單的理解就是介面的實現,請看上面#2中的例子。Oracle開發的鏈接資料庫的jar就是一個服務提供者。
4. ServiceLoader
At the heart of the SPI is the ServiceLoader class. This has the role of discovering and loading implementations lazily. It uses the context classpath to locate providers implementations and put them in an internal cache.
這個類ServiceLoader是SPI的核心。使用懶載入方式發現和載入服務的實現。他通過classpath的定位服務的實現,並將他們載入到jvm中。
舉個例子
創建一個項目:SpiDemo(服務介面)
定義服務介面
package com.ian.demo.spi; public interface MyService { void sayHello(); }
打包並install安裝到本地 .m2的路徑下,pom.xml:
<groupId>com.ian.demo</groupId> <artifactId>SpiDemo</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version>
另起一個新的項目:SpiDemoServicesProvider(服務提供者)
這裡注意下,我們需一個META-INF來暴露我們的介面實現。
並在其中添加一個以介麵包命名的文件
裡面寫上我們的實現類:
com.ian.demo.spi.provider.MyServiceImpl
導入剛才install的jar包,pom.xml:
<dependencies> <dependency> <groupId>com.ian.demo</groupId> <artifactId>SpiDemo</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
實現服務介面:
public class MyServiceImpl implements MyService { @Override public void sayHello() { System.out.println("Hi, There!"); } }
打包安裝到本地m2目錄:
<groupId>com.ian.demo</groupId> <artifactId>SpiDemoServicesProvider</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging>
請注意,我們需要把SPI實現通過META-INF暴露出去,pom中添加: </resources> <resource> <directory>META-INF</directory> <targetPath>/META-INF/</targetPath> <includes> <include>**/*</include> </includes> </resource> </resources>
另起一個項目調用介面:介面真正的使用者
main函數,ClassLoader載入介面實現
public class Main {
public static void main(String[] args) {
ServiceLoader<MyService> myServiceImplSet = ServiceLoader.load(MyService.class);
if(myServiceImplSet.iterator().hasNext()){
myServiceImplSet.iterator().next().sayHello();
}
}
}
導入上面的SPI和SPI實現jar:
<dependencies> <dependency> <groupId>com.ian.demo</groupId> <artifactId>SpiDemoServicesProvider</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.ian.demo</groupId> <artifactId>SpiDemo</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
運行:
Hi, There!
Process finished with exit code 0
總結:
是不是很累贅,為啥要寫三個項目?一個不就行了?還要用ServiceLoader是不是在故弄玄虛。。
當然不是。使用場景就如我上面Oracle的例子。咱們普通開發者要給公司做個項目,需要連接Oracle資料庫,好在java給我們提供了連接介面,並且Oracle提供了實現,我們只要有jre和ojdbc的jar就可以了。有了SPI後,java和Oracle’的開發者就可以分工合作了。這是java典型的解構和強封裝的特性。我們日常的開發中如果有多個系統合作開發的也可以這樣進行。