SPI 与我们熟知的 API 名字上有点相似, SPI 被称为服务提供接口, API 称为应用程序接口, 两者的区别大致可以这样来对比. 假设有客户方和服务方, 彼此通过约定的接口对接.
服务方暴露自己的业务供客户方调用, 则为提供 API 服务.
客户方实现服务方提供的接口, 然后让服务方去调用自己, 则为提供 SPI 服务.
我们平时最常见的 SPI 服务就是 JDBC. 通过统一的 JDBC 规范, 客户方可以自己实现各种数据源. 试想一下, 假设开发者想将数据源从 MySQL 切换到 Oracle, 如果没有使用 JDBC, 切换的过程就需要耗费巨大的人力成本.
如何解决上述的数据源切换问题, 这里需要说一下依赖倒置原则.
高层模块不应该依赖低层模块, 两者都应该依赖抽象
抽象不应该依赖细节
细节应该依赖抽象
JDBC 连接 MySQL
- Class.forName("com.mysql.jdbc.Driver");
- Connection conn = DriverManager.getConnection(
- "jdbc:mysql://localhost:3306/test", "root", "123456");
- Statement stmt = conn.createStatement();
- ResultSet rs = stmt.executeQuery("select * from Users");
下面自己实现一个简易版的 JDBC 程序. 驱动管理程序, 负责加载各 SPI 服务, 用一个 Map 保存所有加载过的驱动程序, key 为 SPI 服务自定义的名称. 在调用 connection 时, 只要 url 中的前缀为驱动名, 则使用该驱动创建连接.
- /**
- * 驱动管理, 负责加载各客户方提供的数据源服务 /
- */
- public class MyDriverManager {
- /**
- * 所有注册的数据源连接服务 /
- */
- private static final Map<String, MyDriver> registerDriver = new HashMap<String, MyDriver>();
- public static void registerDriver(String name, MyDriver driver) {
- registerDriver.put(name, driver);
- }
- public static MyConnection getConnection(String url) {
- for (String key : registerDriver.keySet()) {
- if (url.startsWith(key)) {
- return registerDriver.get(key).getConnection(url);
- }
- }
- throw new RuntimeException("no such provider");
- }
- }
定义驱动接口
- public interface MyDriver {
- /**
- * 获取连接
- */
- MyConnection getConnection(String url);
- }
客户方自己实现的驱动程序, 静态代码块中执行注册服务, 将驱动注册到驱动管理程序中.
- public class MysqlDriver implements MyDriver {
- static {
- MyDriverManager.registerDriver("mysql", new MysqlDriver());
- }
- public MyConnection getConnection(String url) {
- System.out.println("connect to mysql: url =" + url);
- return new MysqlConnection();
- }
- }
实际执行时, 要先使用 Class.forName 加载一下驱动程序, 否则 static 代码块不会执行, 无法将驱动加载到驱动管理程序中.
- public class Main {
- public static void main(String[] args) throws ClassNotFoundException {
- Class.forName("com.github.yaolang.chapter01.spi1.MysqlDriver");
- MyConnection myConnection = MyDriverManager.getConnection("mysql://localhost:8080");
- System.out.println(myConnection);
- }
- }
以下为输出结果
- connect to MySQL: url = MySQL://localhost:8080
- com.GitHub.yaolang.chapter01.spi1.MysqlConnection@60e53b93
来源: https://juejin.im/post/5bd3c553e51d4579ef2c8de4