平常項目開發中,相信很多小夥伴都有過這樣的經歷
-
基礎架構組,或者平臺組的同事會給我們提供一些sdk,我們拿到這些sdk,只需要在pom.xml中添加依賴,就可以直接使用了
-
使用springboot作爲基礎框架,需要某個組件,只需要在pom.xml,添加xxx-starter依賴,就可以直接使用了
-
當需要更換sdk實現方式,xxx-starter組件的時候,只需要更換依賴即可,並不需要修改應用代碼
有沒有領會到以上描述的奧妙之處?即我們的應用代碼,與基礎組件sdk,xxx-starter之間是解耦的,後期代碼維護起來極其方便!
那麼這麼好的解決方案,究竟是如何實現的呢?這就是今天這篇文章我要分享給你的
1.java spi機制與入門案例
1.1.spi介紹
以上我們說的實現應用代碼,與基礎組件之間的解耦,其實我們是在描述一個事實
-
在模塊化、組件化開發的實踐中,需要遵循高內聚、低耦合的思想
-
其中實現解耦的一個基本思路是面向接口編程,即接口隔離的設計原則
-
模塊與模塊之間,組件之間依賴接口,而非具體的實現,即最大化的實現瞭解耦
這是我們在項目中經常推薦的做法,除了提倡面向接口編程這個事實,我們還需要考慮另外一個事實
-
定義好了接口,模塊之間通過依賴接口,實現解耦
-
但是一個接口,可能會有多中實現方案,實現方案A、實現方案B...
-
應用程序在執行的時候,如何知道具體實現的是方案A,還是方案B呢
這就是我要描述的另外一個事實,我們還需要一個發現接口實現的機制。它也就是spi機制。
總結一下,spi的全稱是service provider interface,它是java提供的規範,該規範
-
強調基於接口編程,實現模塊之間的解耦
-
具體實現,首先要定義接口規範
-
然後每個服務提供者,參照規範約定提供接口的具體實現
-
使用者,通過ServiceLoader工具,即可獲取服務提供者的具體實現
-
舉個例子,想想jdbc編程實踐中,不同數據庫廠商提供的驅動實現,比如mysql、oracle
-
再舉個例子,插件化編程的時代,各種插件的使用
以上關於spi的介紹,如果你是第一次關注的話,相信此時一定是蒙圈的,不過不要緊,繼續看完後面的案例, 再回過頭來看 你就能明白了。
1.2.入門案例
1.2.1.代碼接口
通過一個基礎案例,演示java spi的基本使用,案例結構如下
這是一個maven項目,由三個類文件,以及一個配置文件組成
-
Animal:接口規範,spi強調基於接口編程
-
Bird:Animal接口的實現
-
SpiTest:使用者,通過ServiceLoader工具,獲取Animal接口的具體實現
-
META-INF/services/cn.edu.spi.Animal:spi規範的約定,即接口與接口實現的描述文件
1.2.2.代碼
Animal接口
/**
* spi 案例:接口
*
* @author ThinkPad
* @version 1.0
* @date 2021/9/4 15:10
*/
public interface Animal {
/**
* 動物喊話
* @param name
*/
void hello(String name);
}
Bird實現類
/**
* spi案例:接口實現 鳥
*
* @author ThinkPad
* @version 1.0
* @date 2021/9/4 15:12
*/
public class Bird implements Animal{
/**
* 動物喊話
*
* @param name
*/
@Override
public void hello(String name) {
System.out.println("I am small small small Bird! name is:" +name);
}
}
META-INF/services/cn.edu.spi.Animal配置文件
cn.edu.spi.impl.Bird
SpiTest類
/**
* 測試類
*
* @author ThinkPad
* @version 1.0
* @date 2021/9/4 15:15
*/
public class SpiTest {
public static void main(String[] args) {
ServiceLoader<Animal> animals = ServiceLoader.load(Animal.class);
Iterator<Animal> iter = animals.iterator();
while (iter.hasNext()){
Animal animal = iter.next();
animal.hello("Bob");
}
}
}
#運行結果
I am small small small Bird! name is:Bob
Process finished with exit code 0
2.java spi綜合案例
有了spi基礎案例,接下來我們做一個更加接近項目實踐的案例。我們來準備以下一些事情
-
構建sdk接口規範工程:follow-me-spi-animal,它有兩個實現
-
構建sdk接口規範實現工程cat:follow-me-spi-cat,提供具體實現方案1
-
構建sdk接口規範實現工廠dog:follow-me-spi-dog,提供具體實現方案2
-
構建使用者工程:follow-me-springboot-api,實現在cat、dog之間隨意切換實現方式,而不需要修改應用代碼
2.1.工程:follow-me-spi-animal
2.1.1.pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--spi 接口gav信息-->
<groupId>cn.edu.anan</groupId>
<artifactId>follow-me-spi-animal</artifactId>
<version>0.0.1</version>
</project>
2.1.2.接口
package cn.edu.anan;
/**
* spi接口
*
* @author ThinkPad
* @version 1.0
* @date 2021/9/4 16:09
*/
public interface Animal {
/**
* 接口方法
*/
void hello();
}
2.2.工程:follow-me-spi-cat
2.2.1.pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--項目 gav信息-->
<groupId>cn.edu.anan</groupId>
<artifactId>follow-me-spi-cat</artifactId>
<version>0.0.1</version>
<!--公共變量定義-->
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!--spi interface依賴-->
<dependency>
<groupId>cn.edu.anan</groupId>
<artifactId>follow-me-spi-animal</artifactId>
<version>0.0.1</version>
</dependency>
</dependencies>
<!--項目構建插件-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.2.2.接口實現
package cn.edu.anan;
/**
* pi Animal接口實現2
*
* @author ThinkPad
* @version 1.0
* @date 2021/9/4 16:22
*/
public class Cat implements Animal{
/**
* 接口方法
*/
@Override
public void hello() {
System.out.println("I am Cat.喵喵...");
}
}
2.2.3.spi配置
META-INF/services/cn.edu.anan.Animal
cn.edu.anan.Cat
2.3.工程:follow-me-spi-dog
2.3.1.pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--項目 gav信息-->
<groupId>cn.edu.anan</groupId>
<artifactId>follow-me-spi-dog</artifactId>
<version>0.0.1</version>
<!--公共變量定義-->
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!--spi interface依賴-->
<dependency>
<groupId>cn.edu.anan</groupId>
<artifactId>follow-me-spi-animal</artifactId>
<version>0.0.1</version>
</dependency>
</dependencies>
<!--項目構建插件-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.3.2.接口實現
package cn.edu.anan;
/**
* spi Animal接口實現1
*
* @author ThinkPad
* @version 1.0
* @date 2021/9/4 16:13
*/
public class Dog implements Animal{
/**
* 接口方法
*/
@Override
public void hello() {
System.out.println("I am Dog.汪汪...");
}
}
2.3.3.spi配置
META-INF/services/cn.edu.anan.Animal
cn.edu.anan.Dog
2.4.工程:follow-me-springboot-api
2.4.1.依賴follow-me-spi-cat
導入依賴
<!--follow-me-spi-cat依賴-->
<dependency>
<groupId>cn.edu.anan</groupId>
<artifactId>follow-me-spi-cat</artifactId>
<version>0.0.1</version>
</dependency>
應用配置
/**
* spi 配置類
*
* @author ThinkPad
* @version 1.0
* @date 2021/9/4 15:59
*/
@Configuration
public class SpiConfiguration {
@Bean
public Animal animal(){
Animal animal = null;
ServiceLoader<Animal> animals = ServiceLoader.load(Animal.class);
Iterator<Animal> iter = animals.iterator();
while (iter.hasNext()){
animal = iter.next();
break;
}
return animal;
}
}
controller
/**
* spi controller
*
* @author ThinkPad
* @version 1.0
* @date 2021/9/4 15:55
*/
@RestController
@RequestMapping("spi")
@Slf4j
public class SpiController {
@Autowired
private Animal animal;
@RequestMapping("test")
public String test(){
animal.hello();
return animal.getClass().getName();
}
}
啓動應用,訪問端點:http://127.0.0.1:8080/spi/test
響應:cn.edu.anan.Cat
控制檯輸出:I am Cat.喵喵...
2.4.2.依賴follow-me-spi-dog
將依賴換成dog
<!--follow-me-spi-dog依賴-->
<dependency>
<groupId>cn.edu.anan</groupId>
<artifactId>follow-me-spi-dog</artifactId>
<version>0.0.1</version>
</dependency>
<!--follow-me-spi-cat依賴-->
<!--<dependency>
<groupId>cn.edu.anan</groupId>
<artifactId>follow-me-spi-cat</artifactId>
<version>0.0.1</version>
</dependency>-->
啓動應用,訪問端點:http://127.0.0.1:8080/spi/test
響應:cn.edu.anan.Dog
控制檯輸出:I am Dog.汪汪...
3.springboot中的spi機制
在springboot應用中,經常會用到spi機制,我們稱之爲spring factories機制,它的實現原理與java spi機制一致,它提供了
-
SpringFactoriesLoader工具:作用等價於ServiceLoader,用於發現加載接口實現,即服務提供者實現方案
-
META-INF/spring.factories:它約定將spi服務實現,配置到META-INF/spring.factories文件中,以kev/value的形式存在
舉幾個例子,比如說spring-boot
比如說,spring-boot-autoconfigure
具體spring.factories文件內容都比較多,我就不復制粘貼了,我建議你一定要自己打開看一看。另外我寫了一個簡單的參考案例:follow-me-springboot-thirtybean
本文相關源碼,倉庫地址:https://gitee.com/yanghouhua/springboot.git