文章目录
- Java SPI
- SPI源码分析
- JDBC SPI
Java SPI
最近了解到Dubbo / SpringBoot / Nacos / JDBC都使用到了SPI机制,各位大咖都用了,必须好好看一看。
SPI(Service Provider Interface)是JDK内置的一种提供服务发现的机制。
在java中根据一个子类获取其父类或接口信息非常方便,但是根据一个接口获取该接口的所有实现类却没那么容易。
SPI便是解决接口获取实现类的很好的解决方案,我们定义一个接口,
第三方服务提供者可以实现我们的接口,并根据SPI约定在 META-INF/services/ 目录里创建一个以服务接口命名的文件,该文件里写的就是实现该服务接口的具体实现类。我们便可以调用第三方提供的服务。
动手演示下,SPI的使用。
public interface Play {
List<String> play(String keyword);
}
public class IPadPlay implements Play {
@Override
public List<String> play(String keyword) {
System.out.println("Play games with IPad. keyword:" + keyword);
return null;
}
}
public class IPhonePlay implements Play {
@Override
public List<String> play(String keyword) {
System.out.println("Play games with IPhone. keyword:" + keyword);
return null;
}
}
public class MainTest {
public static void main(String[] args) {
System.out.println("Hi, Every");
ServiceLoader<Play> s = ServiceLoader.load(Play.class);
Iterator<Play> iterator = s.iterator();
while (iterator.hasNext()) {
Play search = iterator.next();
search.play("Ready, GO!");
}
}
}
META-INF/services目录下,有2种方式可以存放具体实现类
方式一,建立Play目录(注意是Play接口的全限定名)
然后,Play目录下,建立IPadPlay和IPhonePlay2个文件(2个也是全限定名),文件内容为空,如下图:
方式二,META-INF/services目录下,直接建com.example.springdemo.java.spi1.Play1文件,这个文件里的内容为:
完成以上步骤,程序就可以执行了,输出结果
Hi, Every
Play games with IPad. keyword:Ready, GO!
Play games with IPhone. keyword:Ready, GO!
如此,对于扩展非常方便。
SPI源码分析
ServiceLoader.load(…)的重点是清空了providers,并new了一个LazyIterator
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
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();
}
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
s.iterator()方法
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
接下来的iterator.hasNext()和iterator.next()方法才是重点
iterator.hasNext() // 遍历出所有的具体实现
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
private static final String PREFIX = "META-INF/services/";
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// 加载classpath:META-INF/services/xxx
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;
}
// 解析文件里的值,多行为多个值,返回一个Iterator
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
iterator.next() // 具体实现实例化
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName; // 具体实现的全限定名
nextName = null;
Class<?> c = null;
try {
// 加载class对象.但去初始化,不会执行static块和static变量
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 {
// 类实例化,执行static和构造函数
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
}
JDBC SPI
日常,我们手动连接数据库并获取结果的伪代码如下:
// 注册 JDBC 驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 打开链接
System.out.println("连接数据库...");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/xx","1","2");
// 执行查询
System.out.println(" 实例化Statement对象...");
stmt = conn.createStatement();
String sql;
sql = "SELECT id FROM table1";
ResultSet rs = stmt.executeQuery(sql);
以上这段代码,是怎么利用SPI的呢?
在执行DriverManager.getConnection(…)时,会先执行DriverManager类中的static块
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
在loadInitialDrivers()中,我们重点关注
private static void loadInitialDrivers() {
// ...
// 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.load就是我们上面例子中的用法
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;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
//...
}
我们之前在讲nextService()方法时,Sp = service.cast(c.newInstance()) 会初始化具体实现者,在mysqlJDBC中,会触发以下代码
// com.mysql.cj.jdbc.Driver
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
如此,Driver全部注册并初始化完毕,开始执行DriverManager.getConnection(url, “1”, “2”)方法并返回。