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间传递