SPI概述
1.SPI概念
①全称:Service Provider Interface
②是什么:是Java提供的一套用来被第三方实现或者扩展的接口,从JDBC4.0,JDK6开始Java实现了SPI机制
③作用:用来启用框架扩展和替换组件,为这些被扩展的API寻找服务实现。
2.SPI和API区别
概念 | 实现方 | 调用方 | 使用人员 |
---|---|---|---|
API | 制定接口并完成对接口的实现 | 仅仅依赖接口调用,且无权选择不同实现 | 应用开发人员 |
SPI | 制定接口规范,提供给外部来实现,在调用时则选择自己需要的外部实现 | 框架扩展人员使用 |
3.SPI简单示例
①定义一个接口
package com.lemon.spi;
/**
* @author lemon
* @Desc
* @date 2020-09-22 11:14
*/
public interface RenShu {
void renShu();
}
②定义两个接口实现类
package com.lemon.spi;
/**
* @author lemon
* @Desc
* @date 2020-09-22 11:18
*/
public class ShenLuoTianZheng implements RenShu{
@Override
public void renShu() {
System.out.println("神罗天征1!");
}
}
package com.lemon.spi;
/**
* @author lemon
* @Desc
* @date 2020-09-22 11:19
*/
public class WanXiangTianYin implements RenShu{
@Override
public void renShu() {
System.out.println("万象天引1!");
}
}
③在resources下(非固定,只要相对路径是META-INF/services/接口全路径名)新建package META_INF和services,在services下新建和接口名相同的文件
④新建测试类
package com.lemon.spi;
import java.util.ServiceLoader;
/**
* @author lemon
* @Desc
* @date 2020-09-22 11:10
*/
public class Test {
public static void main(String[] args) {
ServiceLoader<RenShu> renShus = ServiceLoader.load(RenShu.class);
for(RenShu renShu:renShus){
renShu.renShu();
}
}
}
4.SPI源码分析
①ServiceLoader类实现流程
核心代码
①:实例化ServiceLoader及私有内部类LazyIterator
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
②进行迭代,实际调用的是LazyIterator的hasNextService与nextService方法
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//获取目录下所有的类
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
//反射加载类
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
//实例化
S p = service.cast(c.newInstance());
//放进缓存
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
5.JDBC的SPI
com.mysql.cj.jdbc.Driver是其默认的实现类
public class DriverManager {
// List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
private static volatile int loginTimeout = 0;
private static volatile java.io.PrintWriter logWriter = null;
private static volatile java.io.PrintStream logStream = null;
// Used in println() to synchronize logWriter
private final static Object logSync = new Object();
/* Prevent the DriverManager class from being instantiated. */
private DriverManager(){}
/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
static {
//此处进行spi机制调用
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//此处进行迭代扫描
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
/* Load these drivers, so that they can be instantiated.
* It may be the case that the driver class may not be there
* i.e. there may be a packaged driver with the service class
* as implementation of java.sql.Driver but the actual class
* may be missing. In that case a java.util.ServiceConfigurationError
* will be thrown at runtime by the VM trying to locate
* and load the service.
*
* Adding a try catch block to catch those runtime errors
* if driver not available in classpath but it's
* packaged as service and that service is there in classpath.
*/
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
6.SPI优缺点
优点:
1.不需要改动源码就可以实现扩展,解耦。
2.实现扩展对原来的代码几乎没有侵入性。
3.只需要添加配置就可以实现扩展,符合开闭原则。
缺点:
1.不能按需加载,需要遍历所有的实。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。
2.ServiceLoader 线程不安全。