JAVA中的SPI實踐學習
介紹
SPI ,全稱爲 Service Provider Interface,是一種服務發現機制。它通過在ClassPath路徑下的META-INF/services文件夾查找文件,自動加載文件裏所定義的類。
這一機制爲很多框架擴展提供了可能,比如在Dubbo、JDBC中都使用到了SPI機制。其實介紹可以自行百度,下面通過簡單的示例代碼介紹相關功能。
Animal接口
定義一個單獨的工程用於定義Animal接口並定義方法sayHello,代碼如下,非常簡單
package com.zte.sunquan.spi;
/**
* Animal class
*
* @author 10184538
* @date 2019/7/6
*/
public interface Animal {
String sayHello();
}
代碼結構爲:
Animal接口實現
在另一個工程中完成Animal接口的實現,簡單地打印相關信息即可。代碼如下:
package com.zte.sunquan.demo.spi.impl;
import com.zte.sunquan.spi.Animal;
/**
* Bird class
*
* @author 10184538
* @date 2019/7/6
*/
public class Bird implements Animal {
@Override
public String sayHello() {
System.out.println("bird say hello.");
return "bird say hello.";
}
}
在實現的模塊中需要注意的是,爲能SPI能夠發現這個Animal的實現,需要在META-INF.services中增加配置文件,文件名以接口類的全路徑名命名,內容爲實現類的全路徑名。具體爲:
內容爲:
com.zte.sunquan.demo.spi.impl.Bird
調用測試模塊
另外新建一個測試調用的模塊,用於執行Animal中的打印功能,首先需要在pom中加入兩者的依賴
<dependency>
<groupId>com.zte.sunquan.demo</groupId>
<artifactId>spi-demo-interface</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.zte.sunquan.demo</groupId>
<artifactId>spi-demo-impl</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
此時如果沒有SPI機制,當然也是可以調用到Bird裏的animal實現的,最簡單的可以通過
Animal animal=new Bird();
如果是基於Spring或者OSGI的服務引用,可以直接引用到
@Service
private Animal bird;
下面看SPI是怎麼實現的,代碼爲:
package com.zte.sunquan.demo.main;
import java.util.ServiceLoader;
import com.zte.sunquan.demo.spi.impl.Bird;
import com.zte.sunquan.spi.Animal;
/**
* App class
*
* @author 10184538
* @date 2019/7/6
*/
public class App {
public static void main(String[] args) {
ServiceLoader<Animal> shouts = ServiceLoader.load(Animal.class);
for (Animal s : shouts) {
System.out.println(s.getClass().getName());
s.sayHello();
}
}
}
通過ServiceLoader.load可以直接取得所有的animal實現。特別的在基於OSGI的bundle熱加載的機制中,可以利用這點實現更爲靈活的控制。
更進一步
利用SPI的特點實現一個按優點級對數據處理的處理器
具體實現邏輯依賴爲三步
- 定義接口
package com.zte.sunquan.spi;
import com.zte.sunquan.spi.demo.Cow;
/**
* Animal class
*
* @author 10184538
* @date 2019/7/6
*/
public interface Handler {
void process(String input);
void process(Cow input);
default Integer getPriority() {
return 10;
}
}
這邊出於測試,定義了兩個process接口,一個處理基本類型,一個處理對象類型,gePriority爲默認Handler的優先級,因爲需求中要按優先級對handler列表進行排序
- 定義實現
這裏代碼兩個實現的Handler,代碼爲:
package com.zte.sunquan.demo.spi.impl;
import com.zte.sunquan.spi.Handler;
import com.zte.sunquan.spi.demo.Cow;
/**
* AProcessHandler class
*
* @author 10184538
* @date 2019/7/6
*/
public class AProcessHandler implements Handler {
@Override
public void process(String input) {
System.out.println("AProcessHandler:" + input);
input += "AProcessHandler:";
}
@Override
public void process(Cow input) {
System.out.println("AProcessHandler:" + input);
String name = input.getName();
input.setName(name + "AProcessHandler");
}
@Override
public Integer getPriority() {
return 15;
}
}
和
package com.zte.sunquan.demo.spi.impl;
import com.zte.sunquan.spi.Handler;
import com.zte.sunquan.spi.demo.Cow;
/**
* AProcessHandler class
*
* @author 10184538
* @date 2019/7/6
*/
public class BProcessHandler implements Handler {
@Override
public void process(String input) {
System.out.println("BProcessHandler:" + input);
input += "BProcessHandler:";
}
@Override
public void process(Cow input) {
System.out.println("BProcessHandler:" + input);
String name = input.getName();
input.setName(name + "BProcessHandler");
}
@Override
public Integer getPriority() {
return 5;
}
}
可以發現BProcessHandler的優先級要高於AProcessHandler。
同樣在META-INF.services文件中要加入SPI機制可識別的配置文件,
com.zte.sunquan.spi.Handler
com.zte.sunquan.demo.spi.imp.ProcessHandler
com.zte.sunquan.demo.spi.imp.BProcessHandler
- 執行
在執行前,通過==SericeLoader.load(Handler.class)==獲得所有的Handler,還需要排序
所以增加了一個基於泛型的HandlerEx用於對普通Handler包裝並排序,代碼如下:
package com.zte.sunquan.demo.domain;
import java.util.Objects;
import com.zte.sunquan.spi.Handler;
/**
* HandlerExt class
*
* @author 10184538
* @date 2019/7/6
*/
public class HandlerExt<T extends Handler> implements Comparable<HandlerExt<T>> {
private T handler;
private Integer pri;
public HandlerExt(T handler, Integer pri) {
this.handler = handler;
this.pri = pri;
}
public T getHandler() {
return handler;
}
public Integer getPri() {
return pri;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
HandlerExt<?> that = (HandlerExt<?>) o;
return Objects.equals(handler, that.handler) &&
Objects.equals(pri, that.pri);
}
@Override
public int hashCode() {
return Objects.hash(pri);
}
@Override
public int compareTo(HandlerExt<T> o) {
return this.getPri() - o.getPri();
}
}
最後調用代碼如下:
package com.zte.sunquan.demo.main;
import java.util.Collections;
import java.util.List;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.zte.sunquan.demo.domain.HandlerExt;
import com.zte.sunquan.spi.Handler;
import com.zte.sunquan.spi.demo.Cow;
/**
* App class
*
* @author 10184538
* @date 2019/7/6
*/
public class App2 {
//這樣方式可以取代目前的handler機制嗎,省去註冊的步驟(如下是可以的)
//優先級控制
public static void main(String[] args) {
ServiceLoader<Handler> shouts = ServiceLoader.load(Handler.class);
List<Handler> handlers = Lists.newArrayList(shouts.iterator());
List<HandlerExt<Handler>> handlerList = Lists.newLinkedList();
handlers.forEach(handler -> {
handlerList.add(new HandlerExt<>(handler,handler.getPriority()));
});
Collections.sort(handlerList);
ImmutableList<Handler> handlers1 = ImmutableList.copyOf(handlerList.stream()
.map(HandlerExt::getHandler).collect(Collectors.toList()));
String input = "sunquan";
shouts.forEach(handler -> handler.process(input));
//如果入參是對象,這種方式數據變更可以傳遞
Cow cow = new Cow();
cow.setName("sunquan");
handlers1.forEach(handler -> handler.process(cow));
}
}
返回值
- 結論
基於類型的修改無法在handler間傳遞,但對象類型的變化可以在handler間傳遞