一文读懂Java SPI机制

目录

什么是SPI?

SPI常见使用场景

DriverManager实现


什么是SPI?

SPI全称Service Provider Interface,是JDK内置的服务动态发现和替换机制,比如调用方提供一套标准,服务提供方提供了实现,想要在运行时动态的发现替换某一实现就需要用到Java的SPI机制。

示意图

如上图所示SPI的定义在调用方,实现位于具体实现提供方的包里。当服务提供者提供了一种接口的实现后,需要在classpath的META-INF/services目录下创建一个以服务接口命名的文件(如下图),文件的内容是该接口对应的具体实现类的全路径。当需要使用该实现时可以通过java提供的ServLoader进行加载初始化。

上图是mysql驱动提供的实现

SPI常见使用场景

SPI使用场景非常多,比如DriverManager,Spring等,本文将从数据库驱动的使用上来分析一下具体实现。
下面是一个常见的加载mysql驱动的代码:

try {
    Class.forName("com.mysql.cj.jdbc.Driver");
    Connection cn = DriverManager.getConnection("url","user","pwd");
    System.out.println(cn);
} catch (ClassNotFoundException | SQLException e) {
    e.printStackTrace();
}

在执行Class.forName的时候,会执行com.mysql.cj.jdbc.Driver类中的静态代码块将mysql的Driver驱动注册到DriverManager中,DriverManager是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!");
        }
    }
}

DriverManager实现

上面registerDriver方法只是在DriverManager内部维护了一个List,将new 出来的Driver对象放入其中,具体加载的过程也是通过静态代码块来实现的看下面的代码:

/**
     * Load the initial JDBC drivers by checking the System property
     * jdbc.properties and then use the {@code ServiceLoader} mechanism
     */
static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
								
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                /* Load these drivers, so that they can be instantiated.
                 * It may be the case that the driver class may not be there
                 * i.e. there may be a packaged driver with the service class
                 * as implementation of java.sql.Driver but the actual class
                 * may be missing. In that case a java.util.ServiceConfigurationError
                 * will be thrown at runtime by the VM trying to locate
                 * and load the service.
                 *
                 * Adding a try catch block to catch those runtime errors
                 * if driver not available in classpath but it's
                 * packaged as service and that service is there in classpath.
                 */
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

        println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

loadInitialDrivers该方法中可以看到是通过ServiceLoader对具体实现进行加载的,在ServiceLoader内部定义了加载的路径“META-INF/services”,且在内部实现了一个LazyIterator,所以上面代码中跟迭代相关的需要查看ServiceLoader的源码

private static final String PREFIX = "META-INF/services/"; 
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);
     }
 }

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

至此可以看出SPI在DriverManager中的具体实现细节了,其他的应用场景大家如果感兴趣可以自行去琢磨研究一下哈!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有机叶生菜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值