封面来源:碧蓝航线 碧海光粼 活动CG

本文涉及的代码:java8/lambda

0. 前言

在前段时间在《Mockito 实战》一文中对静态方法的 mock 进行了补充。简单回顾下使用前的准备工作,如果导入的 Mockito 依赖是 mockito-inline,直接使用即可;但如果导入的依赖是 mockito-core,除了更换依赖外,还要在项目的 ClassPath 目录下新建 mockito-extensions 目录,然后创建 org.mockito.plugins.MockMakerorg.mockito.plugins.MemberAccessor 文件,并分别追加内容 mock-maker-inlinemember-accessor-module 即可在 Mockito 中 mock 静态方法。

那这是怎么实现的呢?为什么创建两个特定的文件并追加特定的内容就能让 Mockito 支持 mock 静态方法?

先说结论:这利用了类似 SPI 的机制。

那 SPI 又是什么?在其他地方有体现吗?

1. Mockito 与 SPI

先简单看下为什么说 Mockito 利用 SPI 实现了静态方法的 mock。

首先创建了 org.mockito.plugins.MockMakerorg.mockito.plugins.MemberAccessor 两个文件,这两个文件的文件名格式似乎是类的全限定名。以 org.mockito.plugins.MockMaker 为例,搜索一下是否这个类:

1
2
3
4
5
package org.mockito.plugins;

public interface MockMaker {
// 省略抽象方法
}

能够搜到名为 MockMaker 的接口。

既然如此,使用 IDEA 的 Ctrl + Shift + F 全局搜索快捷键搜索 mock-maker-inline 应该也能有所收获。

1
2
3
4
5
6
7
8
9
10
11
12
13
class DefaultMockitoPlugins implements MockitoPlugins {

private static final Map<String, String> DEFAULT_PLUGINS = new HashMap<>();
static final String INLINE_ALIAS = "mock-maker-inline";

static {
// Keep the mapping: plugin interface name -> plugin implementation class name
// ...
DEFAULT_PLUGINS.put(
INLINE_ALIAS, "org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker");
// ...
}
}

果不其然,能够在 DefaultMockitoPlugins 类中找到上述的代码片段。

上述代码片段的内容很好理解,就是往一个 Map 中添加了一些元素,其中一个元素的 key 就是 mock-maker-inline,而这个 key 对应的 value 似乎又是一个类的全限定名。再来搜索一下:

1
2
3
4
5
@SuppressSignatureCheck
class InlineDelegateByteBuddyMockMaker
implements ClassCreatingMockMaker, InlineMockMaker, Instantiator {
// 省略具体内容
}

再回到最开始的 MockMaker 接口,如果查看它的实现类,会发现 InlineDelegateByteBuddyMockMaker 就是它的一个实现类。

MockMaker的实现类

也就是说,org.mockito.plugins.MockMaker 文件的内容 mock-maker-inline 指的就是 MockMaker 接口的实现类 InlineDelegateByteBuddyMockMaker

按照同样的方式,全局搜索下为实现 Mock 静态方法新建的 mockito-extensions 目录名 mockito-extensions

PluginInitializer类中的mockito-extensions

最终可以在 PluginInitializer 类中找到相关代码。新建目录名 mockito-extensions 作为 ClassLoader#getResources() 方法的参数的一部分来加载对应的资源,而 loadImpl() 方法的注释也说此方法等效于 java.util.ServiceLoader#load() 方法:

1
2
3
4
5
6
7
/**
* Equivalent to {@link java.util.ServiceLoader#load} but without requiring
* Java 6 / Android 2.3 (Gingerbread).
*/
public <T> T loadImpl(Class<T> service) {
// 省略具体实现
}

这里的 java.util.ServiceLoader 就是 SPI 的核心类。

简单的分析到此结束,至此基本可以知道 Mockito 实现静态方法的 mock 确实使用到了 SPI 机制。

那 SPI 究竟是什么呢?

2. SPI 的概述与使用

2.1 SPI 的概述

SPI,全称为 Service Provider Interface,是 JDK 内置的一种 服务提供发现机制,常用来启用框架扩展和替换组件,主要被框架的开发人员使用。SPI 机制将装配的控制权移到程序之外,这在模块化设计中尤为重要,其主要目的就是为了 解耦。有点 IoC 的味道了,将装配的控制权移到程序外。

SPI 机制的应用场景有很多,除去前面说的 Mockito 之外,在 JDBC 中也有体现。

2.2 MySQL 与 SPI

以 MySQL 为例,MySQL 的 Java 驱动就使用了 SPI 机制,简单看下。首先导入相关依赖:

1
2
3
4
5
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>

导入成功后,查看 mysql-connector-java 的源码包,可以看到有个 META-INF 目录,在这个目录下有一个 services 目录,里面还有一个名为 java.sql.Driver 的文件,打开这个文件:

1
com.mysql.cj.jdbc.Driver

按照相同的方式,先全局搜索文件名 java.sql.Driver

1
2
3
4
5
6
7
package java.sql;

import java.util.logging.Logger;

public interface Driver {
// 省略抽象方法
}

可以看到在 JDK 的 java.sql 包下存在一个全限定名为 java.sql.Driver 的接口。按照前面的思路,很容易猜到 com.mysql.cj.jdbc.Driver 应该是 java.sql.Driver 接口的实现。全局搜索一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.mysql.cj.jdbc;

import java.sql.DriverManager;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}

static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}

在导入的 mysql-connector-java 依赖中找到了 com.mysql.cj.jdbc.Driver 的具体实现。

com.mysql.cj.jdbc.Driver 内除了一个无参构造方法外,有且仅有一个静态代码块,没有其他的方法。而在静态代码块中调用了 DriverManager#registerDriver(),进入 DriverManager 内部:

1
2
3
4
5
6
7
public class DriverManager {
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
// 省略其他代码
}

DriverManager 内也有一个静态代码块,并在内部调用了静态方法 loadInitialDrivers(),同时能够找到如下代码片段:

loadInitialDrivers方法片段

再次发现 SPI 的核心类 java.util.ServiceLoader,不难得出 JDBC 的加载过程确实也使用到了 SPI 机制。

除此之外,还能发现 java.lang.DriverManager 位于 JDK 的 rt.jar 中,这个类将由启动类加载器进行加载,它能够加载 rt.jar 包之外的类,打破双亲委派模型。

原因是 ServiceLoader 中使用了线程上下文类加载器去加载类(后文细说)。

关于类加载器和双亲委派模型的内容,可以参考《注解、类的加载、反射》一文。

2.3 SPI 的使用示例

以 MySQL 的 Java 驱动作为示例,不难得出 SPI 的使用规律如下:

1、定义一个接口;

2、为第 1 步中定义的接口编写实现类;

3、在 ClassPath 的 META-INF/services 目录(没有目录自行创建)下创建一个文件,文件名为第 1 步定义的接口全限定名,文件内容为定义的接口的实现类的全限定名。如果有多个实现,每个实现类的全限定名占独立的一行;

4、使用 ServiceLoader#load() 加载接口,然后获取含有接口实现的迭代器。

定义一个简单的接口:

1
2
3
4
5
6
7
8
9
package indi.mofan.spi;

/**
* @author mofan
* @date 2022/6/15 12:41
*/
public interface Runnable {
void run();
}

为其编写两个实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package indi.mofan.spi.impl;

import indi.mofan.spi.Runnable;

/**
* @author mofan
* @date 2022/6/15 12:42
*/
public class SimpleRunner implements Runnable {
@Override
public void run() {
System.out.println("Hello, World");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package indi.mofan.spi.impl;

import indi.mofan.spi.Runnable;

/**
* @author mofan
* @date 2022/6/15 12:43
*/
public class DemoRunner implements Runnable {
@Override
public void run() {
System.out.println("Hello, Mofan");
}
}

然后按照第三步的做法,新建文件并编写内容:

indi.mofan.spi.Runnable的内容

最后测试一下:

1
2
3
4
5
6
7
8
9
@Test
public void testSPI() {
ServiceLoader<Runnable> loader = ServiceLoader.load(indi.mofan.spi.Runnable.class);
Iterator<Runnable> iterator = loader.iterator();
while (iterator.hasNext()) {
Runnable runner = iterator.next();
runner.run();
}
}

运行结果如下:

Hello, World
Hello, Mofan    

根据运行结果不难看出通过 ServiceLoader 可以加载定义的实现类。

简化写法

上述的迭代器使用可以使用 Java 提供的 For-Each 语法糖,使代码更加简洁:

1
2
3
4
5
6
7
@Test
public void testSPI() {
ServiceLoader<Runnable> loader = ServiceLoader.load(indi.mofan.spi.Runnable.class);
for (Runnable runner : loader) {
runner.run();
}
}

3. 加载资源的其他方式

在分析 SPI 核心类 ServiceLoader 之前,需要先简单了解下 ClassLoader 类和 Class 类中资源加载的方式,因为在 ServiceLoader 中也使用到了它们,预先了解下还是很有必要的。

3.1 ClassLoader 提供的 API

java.lang.ClassLoader 中,提供了如下六种加载资源的方式:

1
2
3
4
5
6
7
8
9
10
11
12
// 查找具有给定名称的资源
public URL getResource(String name)
// getResource(String name) 的复数版本
public Enumeration<URL> getResources(String name) throws IOException
// 调用 getResource(String name) 后返回的 URL 实例再调用 openStream() 方法
public InputStream getResourceAsStream(String name)

public static URL getSystemResource(String name)

public static Enumeration<URL> getSystemResources(String name) throws IOException

public static InputStream getSystemResourceAsStream(String name)

这六种加载类的方式可以分为两组,一组是实例方法,一组是静态方法。在这两组方法中,又以 getResource()getSystemResource() 最具代表性。

public URL getResource(String name)

1
2
3
4
5
6
7
8
9
10
11
12
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);
}
return url;
}

一股熟悉的味道,该方法使用了类似双亲委派模型的实现,双亲委派模型的相关内容可以参考 注解、类的加载、反射 一文。

通过方法的注释得知,getResource() 方法用于查找具有给定名称的资源,这些资源可以是图像、音频、文本等,资源的名称是通过 / 分割的路径名。简单使用下:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @author mofan
* @date 2022/6/15 17:37
*/
public class MyLoader {
public static void main(String[] args) {
ClassLoader classLoader = MyLoader.class.getClassLoader();
System.out.println(classLoader.getResource(""));
}
}

// file:/D:/Code/MySelf/java8/lambda/target/classes/

很明显,输出的结果是当前 Module 的 ClassPath 路径。

也就是说,getResource() 是基于用户应用程序的 ClassPath 去搜索资源的,并且资源路径需要以 / 进行分割,并且 不能以 / 开头。 比如将上述代码修改为 classLoader.getResource("/") 后的输出结果为 null,表示未找到指定资源。

public static URL getSystemResource(String name)

1
2
3
4
5
6
7
8
9
10
public static URL getSystemResource(String name) {
// 获取系统类加载器
ClassLoader system = getSystemClassLoader();
// 如果为 null,使用启动类加载器加载资源
if (system == null) {
return getBootstrapResource(name);
}
// 如果不为 null,使用实例方法 getResource() 加载资源
return system.getResource(name);
}

简单测试下:

1
2
3
4
5
public static void main(String[] args) {
System.out.println(ClassLoader.getSystemResource(""));
}

// file:/D:/Code/MySelf/java8/lambda/target/classes/

总结

ClassLoader 中加载资源的核心方法是 ClassLoader#getResource(String name),按照双亲委派模型并基于用户应用程序的 ClassPath 路径去搜索资源,资源的名称需要使用 / 分割,并且不能以 / 作为资源名称的起始字符。

如果未找到指定的资源,返回 null,而不是抛出异常。

3.2 Class 提供的 API

java.lang.Class 中也提供了两个加载资源的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public java.net.URL getResource(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResource(name);
}
return cl.getResource(name);
}

public InputStream getResourceAsStream(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResourceAsStream(name);
}
return cl.getResourceAsStream(name);
}

通过比较 ClassClassLoader 中资源加载的方法,前者比后者多做了一步 resolveName()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Add a package name prefix if the name is not absolute Remove leading "/"
* if name is absolute
*/
private String resolveName(String name) {
if (name == null) {
return name;
}
if (!name.startsWith("/")) {
Class<?> c = this;
while (c.isArray()) { // 处理数组
c = c.getComponentType();
}
String baseName = c.getName();
int index = baseName.lastIndexOf('.');
if (index != -1) {
name = baseName.substring(0, index).replace('.', '/')
+"/"+name;
}
} else {
name = name.substring(1);
}
return name;
}

不难看出这个方法对资源名称的处理分为了三种情况,当资源名称:

1、为 null 时,直接返回 null

2、以 / 开头时,去掉开头的 /,基于用户应用程序的 ClassPath 路径搜索资源;

3、未以 / 开头时,以当前类所在包路径为起始路径搜索资源。比如在 indi.mofan.Main.java 中调用了 Main.class.getResource("pic.jpg"),那么需要的加载资源路径为 indi/mofan/pic.jpg

4. ServiceLoader 的源码分析

4.1 构造实例的方式

先看 ServiceLoader 中的变量信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public final class ServiceLoader<S>
implements Iterable<S>
{
// 加载的资源路径前缀,固定是 ClassPath 下的 META-INF/services/ 目录
private static final String PREFIX = "META-INF/services/";

// 被加载的类或接口 Class 信息
// The class or interface representing the service being loaded
private final Class<S> service;

// 加载类时使用的类加载器
// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;

// 访问控制上下文
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;

// 缓存,key 为实现类的全限定名
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// 懒迭代器
// The current lazy-lookup iterator
private LazyIterator lookupIterator;

// 省略其他内容
}

再看看它的构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void reload() {
// 清空缓存
providers.clear();
// 实例化懒迭代器
lookupIterator = new LazyIterator(service, loader);
}

private ServiceLoader(Class<S> svc, ClassLoader cl) {
// 被加载的接口或类 Class 信息不能为 null,否则抛出异常
service = Objects.requireNonNull(svc, "Service interface cannot be null");
// 类加载器为 null,则使用类加载器(应用类加载器)
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
// 实例化访问上下文
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
// 清空缓存并构造懒迭代器实例
reload();
}

reload() 方法在 ServiceLoader 中只被它的构造方法调用,但它的访问修饰符是 public,因此可以使用 ServiceLoader 实例来调用这个方法以清空缓存并构造懒迭代器实例。

ServiceLoader 中有且仅有一个 私有 构造方法,因此是不能通过构造方法去实例化它的。当需要构造它的实例时,需要使用它内部的静态方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}

public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}

public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
ClassLoader cl = ClassLoader.getSystemClassLoader();
ClassLoader prev = null;
while (cl != null) {
prev = cl;
cl = cl.getParent();
}
return ServiceLoader.load(service, prev);
}

load(Class<S> service, ClassLoader loader) 直接调用了仅有的构造方法,需要传入被加载类的 Class 对象和类加载器实例,而 load(Class<S> service) 只需传入被加载类的 Class 对象,默认使用线程上下文类加载器(一般情况下,指向的是应用类加载器或者说系统类加载器)。

loadInstalled(Class<S> service) 方法中能看到“双亲委派模型”的影子。未找到拓展类加载器时,使用系统类加载器;未找到系统类加载器时,使用启动类加载器。

4.2 迭代器方法

1
2
3
public final class ServiceLoader<S> implements Iterable<S> {
// 省略实现
}

ServiceLoader 实现了 Iterable 迭代器接口,必然会重写 iterator() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public Iterator<S> iterator() {
return new Iterator<S>() { // 匿名实现

// 实现类缓存的 Map Entry 实例
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();

public boolean hasNext() {
// 缓存中有下一个实例,返回 true
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() 返回了 Iterator 接口的匿名实现,在判断是否存在下一个实例或获取下一个实例时,先使用缓存判断或获取,然后再使用懒迭代器判断或获取。除此之外,不支持移除操作。

那懒迭代器 LazyIterator 究竟是何方神圣?

4.3 懒迭代器详解

懒迭代器 LazyIteratorServiceLoader 中的私有内部类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private class LazyIterator
implements Iterator<S>
{
// 被加载的接口或类
Class<S> service;
// 加载类时使用的类加载器
ClassLoader loader;
// 需要被加载的资源的 URL 集合
Enumeration<URL> configs = null;
// 需要被加载的实现类的全限定名迭代器
Iterator<String> pending = null;
// 下一个需要被加载的实现类的全限定名
String nextName = null;

private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
// 省略方法的实现
}

再看下内部的方法,方法只有四个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
private boolean hasNextService() {
// 下一个需要被加载的实现类的全限定名不为 null,证明资源中有内容,直接返回 true
if (nextName != null) {
return true;
}
// 需要被加载的资源的 URL 集合为 null 则尝试进行加载
if (configs == null) {
try {
// 拼接资源的名称:META-INF/services + 被加载的接口或类的全限定名
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);
}
}
// 需要被加载的实现类的全限定名迭代器为 null,或者不存在下一个元素
while ((pending == null) || !pending.hasNext()) {
// 判断资源的 URL 集合中是否有元素
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 {
// 利用反射构造 Class 实例
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 {
// 利用 Class#newInstance() 构造实现类实例
S p = service.cast(c.newInstance());
// 添加到缓存中。key 为实现类的全限定名,value 为实现类实例
providers.put(cn, p);
// 返回实例
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}

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);
}
}

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);
}
}

public void remove() {
// 不支持移除操作
throw new UnsupportedOperationException();
}

hasNextService() 中使用到了 ServiceLoader 中的 parse(Class<?> service, URL u) 方法,用于获取含有需要被加载的实现类的全限定名的迭代器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private Iterator<String> parse(Class<?> service, URL u)
throws ServiceConfigurationError
{
InputStream in = null;
BufferedReader r = null;
// 需要被加载的实现类的全限定名集合
ArrayList<String> names = new ArrayList<>();
try {
in = u.openStream();
r = new BufferedReader(new InputStreamReader(in, "utf-8"));
int lc = 1;
// 按行解析
while ((lc = parseLine(service, u, r, lc, names)) >= 0);
} catch (IOException x) {
fail(service, "Error reading configuration file", x);
} finally { // 关闭流
try {
if (r != null) r.close();
if (in != null) in.close();
} catch (IOException y) {
fail(service, "Error closing configuration file", y);
}
}
// 返回集合的迭代器实例
return names.iterator();
}

parse() 方法中又调用了 parseLine(),它也是 ServiceLoader 中的一个私有方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
List<String> names)
throws IOException, ServiceConfigurationError
{
// 读取一行
String ln = r.readLine();
// 读取的行没有内容,返回 -1
if (ln == null) {
return -1;
}
// 如果存在 # 字符,# 支付以后的内容属于注释,被截取掉
int ci = ln.indexOf('#');
if (ci >= 0) ln = ln.substring(0, ci);
// 移除前后的空格
ln = ln.trim();
// 获取被加载的实现类的权限名的长度
int n = ln.length();
if (n != 0) {
// 实现类的全限定名中不能存在空格和制表符
if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
fail(service, u, lc, "Illegal configuration-file syntax");
// 获取实现类全限定名首个字符的码点
// Java 允许使用 Unicode 字符中某些字符作为标识符,因此使用码点判断,码点相关内容可以查阅《Java核心技术 卷一》一书
int cp = ln.codePointAt(0);
// 判断给定的字符是否满足 Java 标识符的第一个字符的要求
if (!Character.isJavaIdentifierStart(cp))
fail(service, u, lc, "Illegal provider-class name: " + ln);
// 循环判断实现类的全限定名中的字符是否满足 Java 标识符的要求(点除外)
for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
cp = ln.codePointAt(i);
if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
fail(service, u, lc, "Illegal provider-class name: " + ln);
}
// 判断缓存中是否存在实现类的全限定名,判断全限定名是否已被加载
if (!providers.containsKey(ln) && !names.contains(ln))
names.add(ln);
}
return lc + 1;
}

LazyIterator 也是 Iterator 接口的实现,它的 Lazy 则体现在它总是在 ServiceLoaderIterator 接口匿名实现在执行 hasNext()next() 时才会“懒判断”是否存在下一个实现类的实例或“懒加载”下一个实现类的实例。

parse() 方法用于读取被加载资源内部的每行实现类的全限定名,而 parseLine() 则是主要用于解析并判断实现类的全限定名是否合法。

以上就是整个 ServiceLoader 中的全部代码,可以看出具体实现并不复杂,但它的功能很强大,被广泛应用于 JDBC、JNDI 等类库中。