SPI机制详细讲解

本文涉及的产品
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: SPI机制详细讲解

文章目录

SPI机制

SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services 文件夹查找文件,自动加载文件里所定义的类。这一机制为很多框架扩展提供了可能,比如在Springboot,Dubbo、JDBC中都使用到了SPI机制。

案例分析

以驱动加载为例。分别定义不同的实现,根据调用方引入不同的实现来进行加载具体的实现类。

建立DriverManager

定义一个获取驱动连接信息的接口。

/**

* 公共的接口 com.elite.common.DriverManager

*/

public interface DriverManager {

   //获取连接信息

   String getConnectionInfo();

}

建立MysqlDriver来实现扩展

/**

* SPI:MySQL对于 getConnectionInfo 的一种实现

*

*/

public class MysqlDriver implements DriverManager

{

   @Override

   public String getConnectionInfo() {

       return "this is mysqldriver";

   }

}

8c91c03785a8472f9b11fe883b61ad8f.png

建立OracleDriver来实现扩展

/**

* 扩展实现  com.elite.oracle.OracleDriver

* com.elite.common.DriverManager

*/

public class OracleDriver implements DriverManager {

   @Override

   public String getConnectionInfo() {

       return "这是oracle数据库连接的扩展实现";

   }

}

9cdd79d9ce80483b84ab90ec1aa6aa5c.png

测试spitest

需要在pom.xml引入具体的实现

       <dependency>

           <groupId>com.elite</groupId>

           <artifactId>MysqlDriver</artifactId>

           <version>1.0-SNAPSHOT</version>

       </dependency>

<!--        <dependency>-->

<!--            <groupId>com.elite</groupId>-->

<!--            <artifactId>OracleDriver</artifactId>-->

<!--            <version>1.0-SNAPSHOT</version>-->

<!--        </dependency>-->

<!--    </dependencies>-->

测试代码

import java.util.Iterator;

import java.util.ServiceLoader;

public class Main {

   public static void main(String[] args) {

       ServiceLoader<DriverManager> providers = ServiceLoader.load(DriverManager.class);

       Iterator<DriverManager> iterator = providers.iterator();

       while(iterator.hasNext()){

           DriverManager next = iterator.next();

           String connectionInfo = next.getConnectionInfo();

           System.out.println(connectionInfo);

       }

   }

}

022672856dde4e3d8aa862194e98cadc.png

源码分析

ServiceLoader类的结构

 //定义了配置文件的路径,这就是为何需要新建配置文件

 private static final String PREFIX = "META-INF/services/";

   // 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

   //ServiceLoader创建时的访问控制上下文

   private final AccessControlContext acc;

   // Cached providers, in instantiation order

   //缓存加载的服务,根据实例化的顺序

   private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

   // The current lazy-lookup iterator

   //加载服务的类

   private LazyIterator lookupIterator;

reload加载类

 //加载lookupIterator  

 public void reload() {

       providers.clear();

       lookupIterator = new LazyIterator(service, loader);

   }

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

   }

LazyIterator类

1.加载配置文件

2.解析配置文件

3.解析每一行

4.获取配置的实现类的类名加入list

5.利用反射加载 c = Class.forName(cn, false, loader);

6.转换为具体的class对象添加到providers S p = service.cast(c.newInstance()); providers.put(cn, p);

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;

       }

       private boolean hasNextService() {

           if (nextName != null) {

               return true;

           }

           if (configs == null) {

               try {

                   //加载完全类限定名 META-INF/servie+接口类名

                   String fullName = PREFIX + service.getName();

                   if (loader == null)

                       //通过类加载转换为URL对象

                       configs = ClassLoader.getSystemResources(fullName);

                   else

                       configs = loader.getResources(fullName);

               } catch (IOException x) {

                   fail(service, "Error locating configuration files", x);

               }

           }

           while ((pending == null) || !pending.hasNext()) {

               if (!configs.hasMoreElements()) {

                   return false;

               }

               //parse加载对应的服务

               pending = parse(service, configs.nextElement());

           }

           nextName = pending.next();

           return true;

       }

       //next方法

       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

       }

   }

parse解析URL对象方法

解析URL对象

private Iterator<String> parse(Class<?> service, URL u)throws ServiceConfigurationError {

       InputStream in = null;

       BufferedReader r = null;

       //解析文件的配置实现类名list

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

   }

parseLine方法

从配置文件解析单行,添加实现类的明名字到list

private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names)

       throws IOException, ServiceConfigurationError {

       //读取行数据

       String ln = r.readLine();

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

           int cp = ln.codePointAt(0);

           if (!Character.isJavaIdentifierStart(cp))

               fail(service, u, lc, "Illegal provider-class name: " + ln);

           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;

   }

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
9月前
|
运维 监控
浅析SPI与CAN通信
SPI是一种常用的MCU与外设的通信方式,英文全称Serial Peripheral Interface。与之前介绍过的UART不同,SPI是串行,全双工,同步通信方式。SPI通常有4根物理连接线,分别是CS片选,SCK时钟,MOSI主机输出从机输入和MISO主机输入从机输出。CS片选是从机选择信号线,低电平有效。当CS为低电平时认为主机目前选中的本从机。SCK是串行时钟线,同步通信需要主从机时钟同步,主机利用SCK线与从机实现时钟同步。时钟由主机产生,决定了通讯的速率。
184 0
|
7月前
|
Dubbo Java 机器人
聊聊 SPI 机制
SPI 的本质是将**接口实现类**的**全限定名配置在文件**中,并由**服务加载器读取配置文件,加载实现类**。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。
|
2月前
|
网络协议 网络安全
SPI 机制
SPI 机制
66 0
|
9月前
|
Dubbo Java 应用服务中间件
JDK SPI、Spring SPI、Dubbo SPI三种机制的细节与演化
Java SPI(Service Provider Interface)是JDK提供的一种服务发现机制,用于在运行时动态加载和扩展应用程序中的服务提供者。
206 0
|
10月前
|
传感器
spi冲突
SPI(Serial Peripheral Interface)是一种串行外设接口,用于在微控制器和其外设之间进行通信。当两个或多个设备使用相同的 SPI 总线时,可能会发生 SPI 冲突。SPI 冲突通常是由于设备之间的时序问题引起的,导致数据传输错误或设备无法正常工作。
100 0
|
12月前
|
设计模式 缓存 安全
迷迷糊糊?似懂非懂?一文让你从此对SPI了如指掌
迷迷糊糊?似懂非懂?一文让你从此对SPI了如指掌
78 0
SPI通信读取W25Q64
参考自野火STM32教程
101 0
|
Java 关系型数据库 MySQL
|
负载均衡 算法 Dubbo
SPI机制
SPI机制
314 0
|
Java 关系型数据库 MySQL