SPI--服务发现机制
一段JDBC代码引发的思考
下面是一段很普通的JDBC使用代码:
@Test
public void testJDBC() throws SQLException, ClassNotFoundException {
String url = "jdbc:mysql://localhost:3307/mls";
String userName = "root";
String password = "123456";
// 1 加载驱动
// Class.forName("com.mysql.cj.jdbc.Driver");
// 2 创建连接
Connection con = DriverManager.getConnection(url, userName, password);
// 3 创建语句
Statement statement = con.createStatement();
String sql = "select * from mlsdb where id=1";
// 4 执行sql,获取结果
ResultSet rs = statement.executeQuery(sql);
while (rs.next()) {
System.out.println(rs.getString("province"));
}
}
其中第一步 Class.forName("com.mysql.cj.jdbc.Driver")通过全限定类名com.mysql.cj.jdbc.Driver,触发了类加载过程,而com.mysql.cj.jdbc.Driver类的源码如下:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
// 将mySql驱动注册到DriverManager中
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
可以看到该类包含一个静态代码块static {}, 这部分代码会被编译器收集到class文件的<clinit>()方法中,并在类加载的初始化阶段执行。执行的结果就是将mysql驱动注册到DriverManager中
细心的读者会发现Class.forName("com.mysql.cj.jdbc.Driver");这一行被注释掉了,那是不是意味着这段代码是非必须的? 如果是非必须的,那步骤2 中DriverManager是如何使用mysql的驱动去创建的连接呢?再深入的思考下,mysql的驱动是何时被偷偷摸摸的注册到DriverManager中的呢?带着这些问题,通过代码我们来一点点分析本文的主人公--SPI(Service Provider Interface 服务发现机制)
源码分析
DriverManager是用于管理JDBC驱动的基础服务类,位于Java.sql包中,因此是由Bootstrap ClassLoader来进行加载。加载该类时,会执行如下代码块:
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
上述静态代码块会执行loadInitialDrivers()方法,该方法用于加载各个数据库驱动。代码如下:
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;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//实例化ServiceLoader对象,并注入线程上下文类加载器和Driver.class
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
//获得迭代器
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(/*查找实现了Driver接口的类*/driversIterator.hasNext()) {
//进行类加载
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
ServiceLoader.load(Driver.class)此方法会实例化一个ServiceLoader对象,并且向其注入线程上下文类加载器和Driver.class;loadedDrivers.iterator():获得ServiceLoader对象的迭代器;driversIterator.hasNext():查找Driver类;driversIterator.next():在实现的“next()”方法中进行类加载,使用上面的线程上下文类加载器。
ServiceLoader.load(Driver.class)
相关调用方法如下:
// 通过当前线程上下文的类加载器ClassLoader 创建一个执行类型 service 的 服务加载器 ServiceLoader
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();
}
// 1 清空缓存
// 2 创建一个懒加载Iterator, 后续调用该Iterator的hasNext()和next()方法才会触发真正的查找和实例化 ServiceProvider提供的服务
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
loadedDrivers.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;
// 发现服务 (LazyIterator.hasNextService())
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext()) return knownProviders.next().getValue();
// 加载 该服务类 (LazyIterator.nextService())
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
LazyIterator--懒加载迭代器
该类是ServiceLoader的内部类
// service provider 需要将配置文件放到 约定的目录下
private static final String PREFIX = "META-INF/services/";
// 用于 查找和实例化 ServiceProvider提供的服务 的 懒加载内部类
private class LazyIterator implements Iterator<S> {
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
// 用于 查找 服务
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 {
//使用loader来进行类加载
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
}
}
经历了上述ServiceLoader类中一系列操作之后(包括服务查找和类加载),位于mysql驱动包中的Driver类会被初始化。该类如下所示
该类定义如下:
绕了一圈我们又回到了文章最开始的地方,静态代码块会在com.mysql.cj.jdbc.Driver类加载的实例化阶段被执行,从而将驱动注册到DriverManager中。
总结
在JDK6里面引进的一个新的特性ServiceLoader,从官方的文档来说,它主要是用来装载一系列的service provider。而且ServiceLoader可以通过service provider的配置文件来装载指定的service provider。当服务的提供者,提供了服务接口的一种实现之后,我们只需要在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。