背景
项目框架中引入了各种第三方组件,比如nacos、seata、mq、Redis、MySQL等等,这些三方组件一般都具有可替代性,如果把这些组件拿来直接使用会对原框架有一定侵入性,后期可能造成框架升级困难、运维复杂等多方面的问题。
使用SPI可以有效解耦三方组件,降低组件变更对原框架的影响,SPI需要原项目框架把可能用到的三方组件的功能抽象出接口,然后不同的组件按照SPI规范提供对应实现,完成对原项目框架的升级拓展。
实现
接口项目
SPI接口
由框架项目抽象出功能接口,例:
package com.test.spi;
public interface IHelloService {
void sayHello(String name);
}
SPI接口工具类
通过这个工具类获取SPI接口的实现类,例:
package com.test.spi;
import java.util.List;
import java.util.ServiceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.CollectionUtils;
public class HelloServiceBuild {
private static IHelloService service;
public static IHelloService build() {
if (null != service) {
return service;
}
ServiceLoader<IHelloService> serviceLoader = ServiceLoader.load(IHelloService.class);
if (serviceLoader.iterator().hasNext()) {
service = serviceLoader.iterator().next();
}
if (null == service) {
List<IHelloService> loadFactories = SpringFactoriesLoader.loadFactories(IHelloService.class, Thread.currentThread().getContextClassLoader());
if (!CollectionUtils.isEmpty(loadFactories)) {
service = loadFactories.get(0);
}
}
return service;
}
}
其中ServiceLoader是java自带获取SPI的方式,SpringFactoriesLoader是有spring提供的获取SPI的方式,可以看出这两种方式都会获取到一个SPI实例的集合,本例只获取集合中的第一个服务实例,两种方式都没有提供通过类名或者是其他方式获取某一确定实例的方式,需要自己拓展,可以参考dubbo的SPI实现过程。
服务项目
接口实现类
是SPI接口的具体实现,最终上面的工具类会实例化出这个类,通过该类完成业务调用,例:
package com.test.spi.provider;
import com.test.spi.IHelloService;
public class HelloServiceProvider implements IHelloService {
@Override
public void sayHello(String name) {
System.out.println("Hello " + name);
}
}
接口配置文件
SPI要完成自动加载实现类,需要借助配置文件,java和spring的配置方式不同,二选一留一个配置即可。
Java配置方式
需要在项目的META-INF/services目录下创建文件名为SPI接口全称的文件,例:
com.test.spi.IHelloService
文件内容为接口实现类的全名
com.test.spi.provider.HelloServiceProvider
Spring配置方式
需要在项目的META-INF目录下创建文件名为spring.factories的文件,
文件内容为SPI接口全名=SPI实现类全名
com.test.spi.IHelloService=com.test.spi.provider.HelloServiceProvider
使用SPI
SPI服务项目一般都是jar包,打包的时候需要确保META-INF文件夹及里面的内容在jar包里,
使用时通过build获取服务提供者
IHelloService service = HelloServiceBuild.build();
if(null != service){
service.sayHello("luck");
}