SPI机制详细讲解

本文涉及的产品
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS AI 助手,专业版
简介: 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;

   }

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
Android开发 Java Maven
Android--增量更新
版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/chaoyu168/article/details/79718196 一、介绍                 当我们发布新版本的时候,一些用户升级并不是很积极,这就造成了新版本的升级率并不高。
1927 0
|
JavaScript 前端开发
CocosCreator 面试题(二)JavaScript中的prototype的理解
CocosCreator 面试题(二)JavaScript中的prototype的理解
519 0
|
7月前
|
存储 缓存 Apache
Apache Iceberg数据湖高级特性及性能调优
性能调优涵盖索引优化、排序策略与元数据管理。通过布隆过滤器、位图索引等提升查询效率,结合文件内/间排序优化I/O与压缩,辅以Z-Order实现多维数据聚集。同时,合理配置元数据缓存与清单合并,加速查询规划。适用于点查、全表扫描及高并发写入场景,显著提升系统性能与资源利用率。
761 0
|
关系型数据库 MySQL
Mysql 查询以某个字符开头的语句和LIKE的使用
Mysql 查询以某个字符开头的语句和LIKE的使用
380 0
|
缓存 监控 开发者
掌握Docker容器化技术:提升开发效率的利器
在现代软件开发中,Docker容器化技术成为提升开发效率和应用部署灵活性的重要工具。本文介绍Docker的基本概念,并分享Dockerfile最佳实践、容器网络配置、环境变量和秘密管理、容器监控与日志管理、Docker Compose以及CI/CD集成等技巧,帮助开发者更高效地利用Docker。
|
JavaScript 安全
TypeScript中any unkown never的区别
TypeScript中any unkown never的区别
|
存储 Cloud Native Linux
音视频 ffmpeg命令图片与视频互转
音视频 ffmpeg命令图片与视频互转
|
存储 缓存 Java
【linux线程(四)】初识线程池&手撕线程池
【linux线程(四)】初识线程池&手撕线程池
|
XML Dubbo Java
SpringBoot与Dubbo整合的几种方式
SpringBoot与Dubbo整合的几种方式
671 2
|
Java 数据库
Springboot 之 Mybatis-plus 多数据源
Springboot 之 Mybatis-plus 多数据源
617 0

热门文章

最新文章