封面来源:碧蓝航线 碧海光粼 活动CG
本文涉及的代码:java8/lambda
0. 前言
在前段时间在《Mockito 实战》一文中对静态方法的 mock 进行了补充。简单回顾下使用前的准备工作,如果导入的 Mockito 依赖是 mockito-inline,直接使用即可;但如果导入的依赖是 mockito-core,除了更换依赖外,还要在项目的 ClassPath 目录下新建 mockito-extensions 目录,然后创建 org.mockito.plugins.MockMaker 和 org.mockito.plugins.MemberAccessor 文件,并分别追加内容 mock-maker-inline 和 member-accessor-module 即可在 Mockito 中 mock 静态方法。
那这是怎么实现的呢?为什么创建两个特定的文件并追加特定的内容就能让 Mockito 支持 mock 静态方法?
先说结论:这利用了类似 SPI 的机制。
那 SPI 又是什么?在其他地方有体现吗?
1. Mockito 与 SPI
先简单看下为什么说 Mockito 利用 SPI 实现了静态方法的 mock。
首先创建了 org.mockito.plugins.MockMaker 和 org.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 就是它的一个实现类。
也就是说,org.mockito.plugins.MockMaker 文件的内容 mock-maker-inline 指的就是 MockMaker 接口的实现类 InlineDelegateByteBuddyMockMaker。
按照同样的方式,全局搜索下为实现 Mock 静态方法新建的 mockito-extensions 目录名 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(),同时能够找到如下代码片段:
再次发现 SPI 的核心类 java.util.ServiceLoader,不难得出 JDBC 的加载过程确实也使用到了 SPI 机制。
除此之外,还能发现 java.lang.DriverManager 位于 JDK 的 rt.jar 中,这个类将由启动类加载器进行加载,它能够加载 rt.jar 包之外的类,打破双亲委派模型。
原因是 ServiceLoader 中使用了线程上下文类加载器去加载类(后文细说)。
关于类加载器和双亲委派模型的内容,可以参考 注解、类的加载、反射 一文。
2.3 SPI 的使用示例
以 MySQL 的 Java 驱动作为示例,不难得出 SPI 的使用规律如下:
定义一个接口;
为第 1 步中定义的接口编写实现类;
在 ClassPath 的 META-INF/services 目录(没有目录自行创建)下创建一个文件,文件名为第 1 步定义的接口全限定名,文件内容为定义的接口的实现类的全限定名。如果有多个实现,每个实现类的全限定名占独立的一行;
使用 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" );
}
}
然后按照第三步的做法,新建文件并编写内容:
最后测试一下:
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);
}
通过比较 Class 和 ClassLoader 中资源加载的方法,前者比后者多做了一步 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 懒迭代器详解
懒迭代器 LazyIterator 是 ServiceLoader 中的私有内部类:
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 则体现在它总是在 ServiceLoader 的 Iterator 接口匿名实现在执行 hasNext() 或 next() 时才会「懒判断」是否存在下一个实现类的实例或「懒加载」下一个实现类的实例。
parse() 方法用于读取被加载资源内部的每行实现类的全限定名,而 parseLine() 则是主要用于解析并判断实现类的全限定名是否合法。
以上就是整个 ServiceLoader 中的全部代码,可以看出具体实现并不复杂,但它的功能很强大,被广泛应用于 JDBC、JNDI 等类库中。