当前位置: 首页 > news >正文

SPI 动态服务发现机制

SPI(Service Provier Interface)是一种服务发现机制,通过ClassPath下的META—INF/services文件查找文件,自动加载文件中定义的类,再调用forName加载;

spi可以很灵活的让接口和实现分离, 让API提供者只提供接口, 第三方来实现。

image

优点:

  • 使用Java SPI机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。

缺点:

  • 虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。
  • 多个并发多线程使用ServiceLoader类的实例是不安全的。

Service 机制分析

第一步:ServiceLoader.load(Class clz);

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

我们看一下 ServiceLoader.load(Driver.class);是如何工作的,注意看第二行代码切换了上下文的 classLoader,一般为 AppClassLoader。

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

ServiceLoader.load(service, cl);接着进入 load 方法

    public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader){return new ServiceLoader<>(service, loader);}

看一下 ServiceLoader 的构造方法,这里可能会进行 ClassLoader 的切换,这不太重要,我们看一下 reload();

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

reload();方法构造了一个LazyIterator,继续看

    public void reload() {providers.clear();lookupIterator = new LazyIterator(service, loader);}

第二步:利用第一步构造出来的LazyIterator,循环Class.forName(),加载接口实现类

Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {// Do nothing}

loadedDrivers.iterator();迭代器在迭代时是会用到第一步创建的lookupIterator = new LazyIterator(service, loader);

    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;return lookupIterator.hasNext();}public S next() {if (knownProviders.hasNext())return knownProviders.next().getValue();return lookupIterator.next();}public void remove() {throw new UnsupportedOperationException();}};}

当我们进行driversIterator.next();时,实际上执行的是lookupIterator.hasNext()

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

当 acc ==null 时,

        private boolean hasNextService() {if (nextName != null) {return true;}if (configs == null) {try {String fullName = PREFIX + service.getName();if (loader == null)configs = ClassLoader.getSystemResources(fullName);elseconfigs = 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;}

当我们进行driversIterator.next();时,等同在调用

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

当 acc==null 时。最关键的逻辑在这里,Class<?> c = Class.forName(cn, false, loader);,然后S p = service.cast(c.newInstance()); 并放入到providers.put(cn, p);

private S nextService() {if (!hasNextService())throw new NoSuchElementException();String cn = nextName;nextName = null;Class<?> c = null;try {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}

使用场景

JDBC 源码补充

jdk 的 rt 包下Driver.class是个接口,全类型限定为java.sql.Driver​:结构如下:

image

第三方mysql-connector-java-8.0.27.jar 下的 Driver 实现了 java.sql.Driver接口。代码如下:

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-8.0.27.jar 的 META-INF/serivces下暴露接口文件,供 SPI 机制发现使用。

image

使用方通过 ServiceLoader 发现所有的接口实现类

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();

SPI打破双亲委派机制

父加载器委托子加载器加载,打破了双亲委派机制。

  1. 由于 rt 包的类加载是 bootstrap classloader
  2. rt 包中DriverManager在loadInitialDrivers中,使用到了 ServiceLoader 触发 Driver.class的加载
  3. 先加载 java.sql.Driver,此时使用到的是 bootstrap classloader
  4. 然后ClassLoader cl = Thread.currentThread().getContextClassLoader();此时的上下文加载器为 App Classloader。

这种父类加载器委托子类加载的行为边打破了双亲委派机制

参考

https://blog.csdn.net/chen462488588/article/details/123894418

相关文章:

  • Docker(八)高级网络配置
  • C语言:函数指针的使用
  • GPT应用开发:编写插件获取实时天气信息
  • (南京观海微电子)——COF介绍
  • 计算机服务器中了mallox勒索病毒怎么办,mallox勒索病毒解密数据恢复
  • K8S-容器运行时(v1.27)
  • 厨艺学习_
  • 人才测评,招聘工程技术经理胜任素质模型与任职资格
  • FineBI实战项目一(25):实战项目一总结
  • Vulnhub-TECH_SUPP0RT: 1渗透
  • 【Python学习】Python学习21- 正则表达式(1)
  • Spark SQL函数定义
  • Day29- 贪心算法part03
  • 系统架构13 - 软件工程(1)
  • 第二章 使用 SQL Search
  • .pyc 想到的一些问题
  • C++回声服务器_9-epoll边缘触发模式版本服务器
  • cookie和session
  • CSS进阶篇--用CSS开启硬件加速来提高网站性能
  • Java 网络编程(2):UDP 的使用
  • javascript面向对象之创建对象
  • React-Native - 收藏集 - 掘金
  • Service Worker
  • spring security oauth2 password授权模式
  • thinkphp5.1 easywechat4 微信第三方开放平台
  • 后端_ThinkPHP5
  • 聊聊hikari连接池的leakDetectionThreshold
  • 前端工程化(Gulp、Webpack)-webpack
  • 前端学习笔记之原型——一张图说明`prototype`和`__proto__`的区别
  • 移动端 h5开发相关内容总结(三)
  • 译自由幺半群
  • - 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》
  • 在Mac OS X上安装 Ruby运行环境
  • 智能网联汽车信息安全
  • 翻译 | The Principles of OOD 面向对象设计原则
  • 摩拜创始人胡玮炜也彻底离开了,共享单车行业还有未来吗? ...
  • ​水经微图Web1.5.0版即将上线
  • # .NET Framework中使用命名管道进行进程间通信
  • ( )的作用是将计算机中的信息传送给用户,计算机应用基础 吉大15春学期《计算机应用基础》在线作业二及答案...
  • (11)工业界推荐系统-小红书推荐场景及内部实践【粗排三塔模型】
  • (a /b)*c的值
  • (超简单)使用vuepress搭建自己的博客并部署到github pages上
  • (附源码)springboot人体健康检测微信小程序 毕业设计 012142
  • (转)fock函数详解
  • (最简单,详细,直接上手)uniapp/vue中英文多语言切换
  • .a文件和.so文件
  • .NET CF命令行调试器MDbg入门(二) 设备模拟器
  • .net MySql
  • .NET Standard 支持的 .NET Framework 和 .NET Core
  • .net 程序发生了一个不可捕获的异常
  • .NET开源的一个小而快并且功能强大的 Windows 动态桌面软件 - DreamScene2
  • .NET开源快速、强大、免费的电子表格组件
  • .NET框架类在ASP.NET中的使用(2) ——QA
  • .net图片验证码生成、点击刷新及验证输入是否正确
  • .NET中GET与SET的用法