前言
ServiceLoader
是Java
提供的一套**SPI(Service Provider Interface,常译:服务发现)**框架,用于实现服务提供方与服务使用方解耦- 在这篇文章里,我将带你理解
ServiceLoader
的原理与设计思想,希望能帮上忙。请点赞,你的点赞和关注真的对我非常重要!
目录
1. SPI 简介
- 定义一个服务的注册与发现机制
- 作用 通过解耦服务提供者与服务使用者,帮助实现模块化、组件化
2. ServiceLoader 使用步骤
我们直接使用JDBC
的例子,帮助各位建立起对ServiceLoader
的基本了解,具体如下:
我们都知道JDBC
编程有五大基本步骤:
- 执行数据库驱动类加载(非必须):
Class.forName("com.mysql.jdbc.driver")
- 连接数据库:
DriverManager.getConnection(url, user, password)
- 创建SQL语句:
Connection#.creatstatement();
- 执行SQL语句并处理结果集:
Statement#executeQuery()
- 释放资源:
ResultSet#close()
、Statement#close()
与Connection#close()
操作数据库需要使用厂商提供的数据库驱动程序,直接使用厂商的驱动耦合太强了,更推荐的方法是使用DriveManager
管理类:
步骤1:定义服务接口
JDBC
抽象出一个服务接口,数据库驱动实现类统一实现这个接口:
public interface Driver { // 创建数据库连接 Connection connect(String url, java.util.Properties info) throws SQLException; // 省略其他方法... } 复制代码
步骤2:实现服务接口
服务提供者(数据库厂商)提供一个或多个实现这个服务的类(驱动实现类),具体如下:
- mysql:
com.mysql.cj.jdbc.Driver.java
public class Driver extends NonRegisteringDriver implements java.sql.Driver { static { try { // 注册驱动 java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } // 省略... } 复制代码
- oracle:
oracle.jdbc.driver.OracleDriver.java
public class OracleDriver implements Driver { private static OracleDriver defaultDriver = null; static { try { if (defaultDriver == null) { //1. 单例 defaultDriver = new OracleDriver(); // 注册驱动 DriverManager.registerDriver(defaultDriver); } } catch (RuntimeException localRuntimeException) { ; } catch (SQLException localSQLException) { ; } } // 省略... } 复制代码
步骤3:注册实现类到配置文件
在java
的同级目录中新建目录resources/META-INF/services
,新建一个配置文件java.sql.Driver
(文件名为服务接口的全限定名),文件中每一行是实现类的全限定名,例如:
com.mysql.cj.jdbc.Driver 复制代码
我们可以解压mysql-connector-java-8.0.19.jar
包,找到对应的META-INF
文件夹。
步骤4:加载服务
// DriverManager.java static { loadInitialDrivers(); } private static void loadInitialDrivers() { // 省略次要代码... AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { // 使用ServiceLoader遍历实现类 ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); // 获得迭代器 Iterator<Driver> driversIterator = loadedDrivers.iterator(); // 迭代 try{ while(driversIterator.hasNext()) { driversIterator.next(); // 疑问:为什么没有任何处理? } } catch(Throwable t) { // Do nothing } return null; } }); // 省略次要代码... } 复制代码
可以看到,DriverManager
的静态代码块调用loadInitialDrivers ()
,方法内部通过ServiceLoader
提供的迭代器Iterator<Driver>
遍历了所有驱动实现类,但是为什么在迭代里没有任何操作呢?
while(driversIterator.hasNext()) { driversIterator.next(); // 疑问:为什么没有任何处理? } 复制代码
在下一节,我们深入ServiceLoader
的源码来解答这个问题。
3. ServiceLoader 源码解析
# 提示 #
ServiceLoader
中有一些源码使用了安全检测,如AccessController.doPrivileged()
,在以下代码摘要中省略
- 工厂方法
ServiceLoader
提供了三个静态泛型工厂方法,内部最终将调用ServiceLoader.load(Class,ClassLoader)
,具体如下:
// 1. public static <S> ServiceLoader<S> loadInstalled(Class<S> service) { // 使用双亲委派模型中最顶层的ClassLoader ClassLoader cl = ClassLoader.getSystemClassLoader(); ClassLoader prev = null; while (cl != null) { prev = cl; cl = cl.getParent(); } return ServiceLoader.load(service, prev); } // 2. public static <S> ServiceLoader<S> load(Class<S> service) { // 使用线程上下文类加载器 ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); } // 3. public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){ return new ServiceLoader<>(service, loader); } 复制代码
可以看到,三个方法仅在传入的ClassLoader
参数有区别,若还不了解ClassLoader
,请务必阅读[《Java | 带你理解 ClassLoader 的原理与设计思想》](Editting...)
- 构造方法
private final Class<S> service; private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; reload(); } public void reload() { // 清空 providers providers.clear(); // 实例化 LazyIterator lookupIterator = new LazyIterator(service, loader); } 复制代码
可以看到,ServiceLoader
的构造器中创建了LazyIterator
迭代器的实例,这是一个“懒加载”的迭代器。那么这个迭代器在哪里使用的呢?继续往下看~
- 外部迭代器
private LazyIterator lookupIterator; // 返回一个新的迭代器,包装了providers和lookupIterator public Iterator<S> iterator() { return new Iterator<S>() { Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); public boolean hasNext() { // 优先从knownProviders取 if (knownProviders.hasNext()) return true; return lookupIterator.hasNext(); } public S next() { // 优先从knownProviders取 if (knownProviders.hasNext()) return knownProviders.next().getValue(); return lookupIterator.next(); } public void remove() { throw new UnsupportedOperationException(); } }; } 复制代码
可以看到,ServiceLoader
里有一个泛型方法Iterator<S> iterator()
,它包装了providers
集合迭代器和lookupIterator
两个迭代器,迭代过程中优先从providers
获取元素。
为什么要优先从providers
集合中取元素呢?阅读源码发现,LazyIterator#next()
会将每轮迭代中取到的元素put
到providers
集合中,providers
其实是LazyIterator
的内存缓存。
- 内部迭代器
# 提示 #
以下代码摘要中省略了源码中的
try-catch
// ServiceLoader.java private static final String PREFIX = "META-INF/services/"; private class LazyIteratorimplements 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 boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { // configs 未初始化才执行 // 配置文件:META-INF/services/服务接口的全限定名 String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } // 分析点1:解析配置文件资源 pending = parse(service, configs.nextElement()); } // nextName:下一个实现类的全限定名 nextName = pending.next(); return true; } private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; // 1. 使用类加载器loader加载 Class<?> c = Class.forName(cn, false, loader); if (!service.isAssignableFrom(c)) { ClassCastException cce = new ClassCastException(service.getCanonicalName() + " is not assignable from " + c.getCanonicalName()); fail(service, "Provider " + cn + " not a subtype", cce); } // 2. 根据Class实例化服务实现类 S p = service.cast(c.newInstance()); // 3. 服务实现类缓存到 providers providers.put(cn, p); return p; } public boolean hasNext() { return hasNextService(); } public S next() { return nextService(); } public void remove() { throw new UnsupportedOperationException(); } } // 分析点1:解析配置文件资源,实现类的全限定名列表迭代器 private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError { // 使用 UTF-8 编码输入配置文件资源 InputStream in = u.openStream(); BufferedReader r = new BufferedReader(new InputStreamReader(in, "utf-8")); ArrayList<String> names = new ArrayList<>(); int lc = 1; while ((lc = parseLine(service, u, r, lc, names)) >= 0); return names.iterator(); } 复制代码
4. ServiceLoader 要点
理解ServiceLoader
源码之后,我们总结要点如下:
- 约束
- 服务实现类必须实现服务接口
S
,参见源码:if (!service.isAssignableFrom(c))
- 服务实现类需包含无参的构造器,
ServiceLoader
将通过该无参的构造器来创建服务实现者的实例,参见源码:S p = service.cast(c.newInstance());
- 配置文件需要使用
UTF-8
编码,参见源码:new BufferedReader(new InputStreamReader(in, "utf-8"));
- 懒加载
ServiceLoader
使用“懒加载”的方式创建服务实现类实例,只有在迭代器推进的时候才会创建实例,参见源码:nextService()
- 内存缓存
ServiceLoader
使用LinkedHashMap
缓存创建的服务实现类实例,LinkedHashMap
在二次迭代时会按照Map#put
执行顺序遍历 - 服务实现的选择当存在多个提供者时,服务消费者模块不一定要全部使用,而是需要根据某些特性筛选一种最佳实现。
ServiceLoader
的机制只能在遍历整个迭代器的过程中,从发现的实现类中决策出一个最佳实现,例如使用Charset.forName(String)
获得Charset
实现类:
// 服务接口 public abstract class CharsetProvider { public abstract Charset charsetForName(String charsetName); // 省略其他方法... } // Charset.java public static Charset forName(String charsetName) { // 以下只摘要与ServiceLoader有关的逻辑 ServiceLoader<CharsetProvider> sl = ServiceLoader.load(CharsetProvider.class, cl); Iterator<CharsetProvider> i = sl.iterator(); for (Iterator<CharsetProvider> i = providers(); i.hasNext();) { CharsetProvider cp = i.next(); // 满足匹配条件,return Charset cs = cp.charsetForName(charsetName); if (cs != null) return cs; } } 复制代码
ServiceLoader
没有提供服务的注销机制服务实现类实例被创建后,它的垃圾回收的行为与Java
中的其他对象一样,只有这个对象没有到GC Root
的强引用,才能作为垃圾回收。
5. 问题回归
现在我们回到阅读DriverManager
源码提出的疑问:
while(driversIterator.hasNext()) { driversIterator.next(); // 疑问:为什么没有任何处理? } 复制代码
为什么next()
操作既不取得服务实现类对象,后续也没有任何处理呢?我们再回去看下LazyIterator#next()
的源码:
// ServiceLoader.java // next() 直接调用 nextService() private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; // 1. 使用类加载器loader加载 Class<?> c = Class.forName(cn, false, loader); if (!service.isAssignableFrom(c)) { ClassCastException cce = new ClassCastException(service.getCanonicalName() + " is not assignable from " + c.getCanonicalName()); fail(service, "Provider " + cn + " not a subtype", cce); } // 2. 根据Class实例化服务实现类 S p = service.cast(c.newInstance()); // 3. 服务实现类缓存到 providers providers.put(cn, p); return p; } 复制代码
- 使用类加载器loader加载:
Class<?> c = Class.forName(cn, false, loader);
这里传参使用false
,类加载器将执行加载 -> 链接,不会执行初始化 - 根据 Class 实例化服务实现类 由于创建类实例前一定会保证类加载完成,因此这里类加载器隐式执行了初始化,这就包括了类的静态代码块执行
回过头看com.mysql.cj.jdbc.Driver
和oracle.jdbc.driver.OracleDriver
源码,我们都发现了类似的静态代码块:
static { try { // 注册驱动 java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } 复制代码
可以看到,它们都调用了DriverManager#registerDriver
注册了一个服务实现类实例,保存在CopyOnWriteArrayList
中,后续获取数据库连接时是从这个列表中获取数据库驱动。现在,你理解了吗?
6. 总结
ServiceLoader
基于 SPI 思想,可以实现服务提供方与服务使用方解耦,是模块化、组件化的一种实现方式ServiceLoader
是一个相对简易的框架,往往只在Java
源码中使用,为了满足复杂业务的需要,一般会使用提供SPI
功能的第三方框架,例如后台的Dubbo
、客户端的ARouter
与WMRouter
等
在后面的文章中,我将与你探讨ARouter
与WMRouter
的源码实现,欢迎关注彭旭锐的博客。