共计 8617 个字符,预计需要花费 22 分钟才能阅读完成。
请点赞关注,你的反对对我意义重大。
🔥 Hi,我是小彭。本文已收录到 Github · AndroidFamily 中。这里有 Android 进阶成长常识体系,有气味相投的敌人,关注公众号 [彭旭锐] 带你建设外围竞争力。
前言
大家好,我是小彭。
过来两年,咱们在掘金平台上公布过一些文章,小彭也受到了大家的意见和激励。最近,小彭会陆续搬运到公众号上。
学习路线图:
1. 意识服务发现?
1.1 什么是服务发现
服务发现(Service Provider Interface,SPI)是一个服务的注册与发现机制,通过解耦服务提供者与服务使用者,实现了服务创立 & 服务应用的关注点拆散。 服务提供模式能够为咱们带来以下益处:
- 1、在内部注入或配置依赖项,因而咱们能够重用这些组件。当咱们须要批改依赖项的实现时,不须要大量批改很多处代码,只须要批改一小部分代码;
- 2、能够注入依赖项的模仿实现,让代码测试更加容易。
服务发现示意图
1.2 服务发现和依赖注入的区别
服务发现和依赖注入都是管制反转 Ioc 的实现模式之一。 IoC 能够认为是一种设计模式,然而因为实践成熟的工夫绝对较晚,所以没有蕴含在《设计模式 · GoF》之中,即: 当依赖方须要应用依赖项时,不再间接结构对象,而是由内部 IoC 容器来创立并提供依赖。
- 1、服务提供模式: 从内部服务容器抓取依赖对象,调用方能够“被动”管制申请依赖对象的机会;
- 2、依赖注入: 并以参数的模式注入依赖对象,调用方“被动”接管内部注入的依赖对象。
2. JDK ServiceLoader 的应用步骤
在剖析 ServiceLoader 的应用原理之前,咱们先来介绍下 ServiceLoader 的应用步骤。
咱们间接以 JDBC 作为例子,其中「2、连贯数据库」外部就是用了 ServiceLoader。为什么连贯数据库须要应用 SPI 设计思维呢?因为操作数据库须要应用厂商提供的数据库驱动程序,如果间接应用厂商的驱动耦合太强了,而应用 SPI 设计就可能实现服务提供者与服务使用者解耦。
以下为应用步骤,具体分为 5 个步骤:
- 1、(非必须)执行数据库驱动类加载:
Class.forName("com.mysql.jdbc.driver")
- 2、连贯数据库:
DriverManager.getConnection(url, user, password)
- 3、创立 SQL 语句:
Connection#.creatstatement();
- 4、执行 SQL 语句并处理结果集:
Statement#executeQuery()
- 5、开释资源:
ResultSet#close()
Statement#close()
Connection#close()
上面,咱们一步步手写 JDBC 中对于 ServiceLoader 的相干源码:
步骤 1:定义服务接口
定义一个驱动接口,这个接口将由数据库驱动实现类实现。在服务发现框架中,这个接口就是服务接口。
public interface Driver {
// 创立数据库连贯
Connection connect(String url, java.util.Properties info);
...
}
步骤 2:实现服务接口
数据库厂商提供一个或多个实现 Driver 接口的驱动实现类,以 mysql 和 oracle 为例:
- mysql:
com.mysql.cj.jdbc.Driver.java
// 已简化
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
// 注册驱动
java.sql.DriverManager.registerDriver(new Driver());
}
...
}
- oracle:
oracle.jdbc.driver.OracleDriver.java
// 已简化
public class OracleDriver implements Driver {
private static OracleDriver defaultDriver = null;
static {if (defaultDriver == null) {
// 1、单例
defaultDriver = new OracleDriver();
// 注册驱动
DriverManager.registerDriver(defaultDriver);
}
}
...
}
步骤 3:注册实现类到配置文件
在工程目录 java
的同级目录中新建目录 resources/META-INF/services
,新建一个配置文件 java.sql.Driver
(文件名为服务接口的全限定名),文件中每一行是实现类的全限定名,例如:
com.mysql.cj.jdbc.Driver
咱们能够解压 mysql-connector-java-8.0.19.jar
包,找到对应的 META-INF 文件夹。
步骤 4:(应用方)加载服务
DriverManaer.java
// 已简化
static {loadInitialDrivers();
}
// 入口
private static void loadInitialDrivers() {
...
// 读取 "jdbc.drivers" 属性
String drivers = System.getProperty("jdbc.drivers");
// 1、应用 ServiceLoader 遍历 Driver 服务接口的实现类
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
// 2、取得迭代器
Iterator<Driver> driversIterator = loadedDrivers.iterator();
// 3、迭代(ServiceLoader 外部会通过反射)while(driversIterator.hasNext()) {driversIterator.next();
}
return null;
...
}
能够看到,DriverManager 被类加载时(static{})会调用 loadInitialDrivers()
。这个办法外部通过 ServiceLoader 提供的迭代器 Iterator<Driver> 遍历了所有驱动实现类。那么,ServiceLoader 是如何实例化 Driver 接口的实现类的呢?下一节,咱们会深刻 ServiceLoader 的源码来解答这个问题。
3. ServiceLoader 源码解析
3.1 ServiceLoader 入口办法
ServiceLoader 提供了三个动态泛型工厂办法,外部最终将调用 ServiceLoader.load(Class, ClassLoader)
,其中第一个参数就是服务接口的 Class 对象。
ServiceLoader.java
// 办法 1:public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
// 应用 SystemClassLoader 类加载器
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);
}
能够看到,三个办法仅在传入的类加载器不同,最终只是返回了一个面向服务接口 S 的 ServiceLoader 对象。咱们先看一下结构器里做了什么工作。
3.2 ServiceLoader 构造方法
ServiceLoader.java
// 已简化
private final Class<S> service;
// 服务实现缓存
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
private ServiceLoader(Class<S> svc, ClassLoader cl) {
// 1、类加载器
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
// 2、清空 providers
providers.clear();
// 3、实例化 LazyIterator
lookupIterator = new LazyIterator(service, loader);
}
能够看到,ServiceLoader 的结构器中次要就是实例化了一个 LazyIterator
迭代器的实例,这是一个「懒加载」的迭代器。这个迭代器里做了什么呢?咱们持续往下看
3.3 LazyIterator 迭代器
ServiceLoader.java
// -> 3、实例化 LazyIterator
// 前文提到的配置文件门路
private static final String PREFIX = "META-INF/services/";
private class LazyIterator implements Iterator<S> {
// 服务接口 Class 对象
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
// pending、nextName:用于解析配置文件中的服务实现类名
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
// 3.1 判断是否有下一个服务实现
@Override
public boolean hasNext() {return hasNextService();
}
// 3.2 返回下一个服务实现
@Override
public S next() {return nextService();
}
@Override
public void remove() {throw new UnsupportedOperationException();
}
// -> 3.1 判断是否有下一个服务实现
private boolean hasNextService() {if (nextName != null) {return true;}
if (configs == null) {
// 3.1.1 拼接配置文件门路:META-INF/services/ 服务接口的全限定名
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
}
// 3.1.2 parse:解析配置文件资源的迭代器
while ((pending == null) || !pending.hasNext()) {if (!configs.hasMoreElements()) {return false;}
pending = parse(service, configs.nextElement());
}
// 3.1.3 下一个实现类的全限定名
nextName = pending.next();
return true;
}
// 3.2 返回下一个服务实现
private S nextService() {if (!hasNextService()) throw new NoSuchElementException();
String cn = nextName;
nextName = null;
// 3.2.1 应用类加载器 loader 加载
Class<?> c = Class.forName(cn, false /* 不执行初始化 */, loader);
if (!service.isAssignableFrom(c)) {
// 查看是否实现 S 接口
ClassCastException cce = new ClassCastException(service.getCanonicalName() + "is not assignable from" + c.getCanonicalName());
fail(service, "Provider" + cn + "not a subtype", cce);
}
// 3.2.2 应用反射创立服务类实例
S p = service.cast(c.newInstance());
// 3.2.3 服务实现类缓存到 providers
providers.put(cn, p);
return p;
}
}
// -> 3.1.2 parse:解析配置文件资源的迭代器
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();}
以上代码曾经十分简化了,LazyIterator 的要点如下:
-
hasNext() 判断逻辑:
- 3.1.1 拼接配置文件门路:「META-INF/services/ 服务接口的全限定名」;
- 3.1.2 解析配置文件资源的迭代器;
- 3.1.3 找到下一个实现类的全限定名。
-
next() 逻辑:
- 3.2.1 应用类加载器 loader 加载(不执行初始化);
- 3.2.2 应用反射创立服务类实例;
- 3.2.3 服务实现类缓存到 providers。
小结一下:LazyInterator 会解析「META-INF/services/ 服务接口的全限定名」配置,遍历每个服务实现类全限定类名,执行类加载(未初始化),最初将服务实现类缓存到 providers。
那么,这个迭代器在哪里应用的呢?持续往下看~
3.4 包装迭代器
其实 ServiceLoader 自身就是实现 Iterable 接口的:
ServiceLoader.java
public final class ServiceLoader<S> implements Iterable<S>
让咱们来看看 ServiceLoader 中的 Iterable#iterator()
是如何实现的:
private LazyIterator lookupIterator;
// 4、返回一个新的迭代器,包装了 providers 和 lookupIterator
public Iterator<S> iterator() {return new Iterator<S>() {// providers 就是上一节 next() 中缓存的服务实现
Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
@Override
public boolean hasNext() {
// 4.1 优先从 knownProviders 取,再从 LazyIterator 取
if (knownProviders.hasNext()) return true;
return lookupIterator.hasNext();}
@Override
public S next() {
// 4.2 优先从 knownProviders 取,再从 LazyIterator 取
if (knownProviders.hasNext()) return knownProviders.next().getValue();
return lookupIterator.next();}
@Override
public void remove() {throw new UnsupportedOperationException();
}
};
}
能够看到,ServiceLoader 里有一个泛型办法 Iterator\<S\> iterator(),它包装了 providers 汇合迭代器和 lookupIterator 两个迭代器。对于曾经“发现”的服务实现类会被缓存到 providers 汇合中,包装类的作用就是优先读取缓存而已。
4. ServiceLoader 源码剖析总结
了解 ServiceLoader 源码之后,咱们总结要点如下:
4.1 束缚
1、服务实现类必须实现服务接口 S(if (!service.isAssignableFrom(c))
);
2、服务实现类需蕴含无参的结构器,LazyInterator 是反射创立实现类市里的(S p = service.cast(c.newInstance())
);
3、配置文件须要应用 UTF-8 编码(new BufferedReader(new InputStreamReader(in, "utf-8"))
)。
4.2 懒加载
ServiceLoader 应用「懒加载」的形式创立服务实现类实例,只有在迭代器推动的时候才会创立实例(nextService()
)。
4.3 内存缓存
ServiceLoader 应用 LinkedHashMap 缓存创立的服务实现类实例。
提醒:LinkedHashMap 在迭代时会依照 Map#put 执行程序遍历。
4.4 没有服务登记机制
服务实现类实例被创立后,它的垃圾回收的行为与 Java 中的其余对象一样,只有这个对象没有到 GC Root 的强援用,能力作为垃圾回收。而 ServiceLoader 外部只有一个办法来齐全革除 provices 内存缓存。
public void reload() {providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
4.5 没有服务筛选机制
当存在多个提供者时,ServiceLoader 没有提供筛选机制,应用方只能在遍历整个迭代器中的所有实现,从发现的实现类中决策出一个最佳实现。举个例子,咱们能够应用字符集的示意符号来取得一个对应的 Charset 对象:Charset.forName(String)
,这个办法外面就只会抉择匹配的 Charaset 对象。
CharsetProvider.java
服务接口
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;
}
}
5. 总结
- 服务发现 SPI 是管制反转 IoC 的实现形式之一,而 ServiceLoader 是 JDK 中实现的 SPI 框架。ServiceLoader 自身就是一个 Iterable 接口,迭代时会从
META-INF/services
配置中解析接口实现类的全限定类名,应用反射创立服务实现类对象; -
ServiceLoader 是 JDK 自带的服务发现框架,原理也绝对简略,比方 Charset、AnnocationProcessor 等性能都是基于 ServiceLoader 实现的。另一方面,ServiceLoader 是一个绝对繁难的框架,为了满足简单业务的须要,个别会应用其余第三方框架,例如后盾的 Dubbo、客户端的 ARouter 与 WMRouter 等。
我是小彭,带你构建 Android 常识体系。技术和职场问题,请关注公众号 [彭旭锐] 私信我发问。