Java Spi的原理和实践
Java SPI的概念和术语
SPI: 全称是 Service Provider Interface
它是从 java 6 开始引入的,是一种基于 ClassLoader 来发现并加载服务的机制。
一个标准的SPI,由三个组件构成,分别是:
Service
Service,是一个公开的接口或抽象类,定义了一个抽象的功能模块。
Service Provider
Service Provider,则是 Service 接口的一个实现类。
ServiceLoader
ServiceLoader,是SPI机制中的核心组件,负责在运行时发现并加载 Service Provider。
Java SPI 的运行流程:
思考
思考:
1. SPI 的作用是什么?解决了什么问题?
2. 如果要实现一个 SPI 应用,需要怎么做?
3. 背后的设计思想是什么?我们能得到什么启示?
Java SPI 在 JDBC 中的应用
JDBC,全称是 Java DataBase Connectivity
- JDBC 即使用 Java 语言来访问数据库的一套 API
- 每个数据库厂商都会提供各自的 JDBC 实现
JDBC 架构图:
SPI 之前 JDBC 访问数据库
应用程序
- Load Driver
- Open Connection
- Execute Statement
- Process ResultSet
- Close Connection
// 1. 加载驱动(使用 Class.forName
// JDBC 要求 Driver 实现类在类加载的时候,能将自身的实例对象自动注册到 DriverManager 中,从而加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
Class.forName("com.mysql.jdbc.OracleDriver");
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
// 2. 打开数据库连接
Connection conn = DriverManager.getConnection(url, user, password);
// 3. 执行SQL语句(使用 Statement 类)
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery("SELECt * FROM table_user LIMIT 1");
// 4. 处理结果集(使用 ResultSet 类)
while (rs.next()) {
System.out.println(rs.getInt("id));
}
// 5. 关闭连接
rs.close();
st.close();
conn.close();
Java SPI 出现后 JDBC 访问数据库
// 1. 引入数据库驱动的 JAR 包(例如 mysql-connector-java.<version>.jar)
// 2. 打开数据库连接
Connection conn = DriverManager.getConnection(url, user, passowrd);
// 3. 执行SQL语句(使用 Statement 类)
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery("SELECt * FROM table_user LIMIT 1");
// 4. 处理结果集(使用 ResultSet 类)
while (rs.next()) {
System.out.println(rs.getInt("id));
}
// 5. 关闭连接
rs.close();
st.close();
conn.close();
Java SPI 的三大规范要素
1. 规范的配置文件:
- 文件路径:必须在JAR包中的 META-INF/services 目录下
- 文件名称:Service 接口的全限定名
- 文件内容:Service 实现类(即Service Provider类)的全限定名。如果有多个实现类,那么每一个实现类在文件中单独占据一行
2. Service Provider 类必须具备无参的默认构造方法:
- Service 接口的实现类,即 Service Provider 类,必须具备无参的默认构造方法。因为随后通过反射技术实例化它时,是不带参数的
3. 保证能加载到配置文件和 Service Provider 类
- 方式一:将 Service Provider 的 JAR 包放到 classpath 中(最常用)
- 方式二:将 JAR 包安装到 JRE 的扩展目录中
- 方式三:自定义一个 ClassLoader
总结
作用
提供了一种组建发现和注册的方式,可以用于实现各种插件,或者灵活替换框架所使用的组建
优点
基于面向接口编程,优雅地实现模块之间的解耦
设计思想
面向接口 + 配置文件 + 反射技术
应用场景
JDBC SLF4J Servlet容器初始化 ...
Java SPI 与 SpringBoot 自动配置
SpringBoot 自动配置,即大名鼎鼎的 Auto-Configuration:
- 它是指基于你引入的依赖 Jar 包,对 SpringBoot 应用进行自动配置
- 提供了自动配置功能的依赖 Jar 包,通常被称为 starter,例如 mybatis-spring-boot-starter 等等
Java SPI 的设计思想
- 使用约定的配置文件
- 谁提供 Jar 包,也负责提供配置文件
- 高内聚低耦合,代码+配置一肩挑
- 使用 ClassLoader 的 getResource 和 getResources 方法,来读取 classpath 中的配置文件
SpringBoot 自动配置的核心实现
- 使用约定的配置文件:
- 文件路径是:META-INF/spring.factories
- 文件内容是"key=value1,value2,...valueN"的格式。其中 key 是 "EnableAutoConfiguration" 的类名,value 是自动配置的类名
- 提供自动配置类的 Jar 包中,也同时提供配置文件 META-INF/spring.factories
- 和 SPI 一样:使用 ClassLoader 的 getResource 和 getResources 方法,来读取 classpath 中的配置文件