Java之SPI机制深入研究

本文详细介绍了Java SPI(Service Provider Interface)机制,包括其工作原理、如何手动实现SPI示例、源码分析以及实战案例。通过一个自定义数据库驱动类实现JDBC Driver接口,展示了SPI如何加载和注册驱动。同时,分析了`DriverManager`的源码,解释了类加载器的角色。Java SPI机制允许在运行时动态加载服务实现,提高了系统的可扩展性和灵活性。
摘要由CSDN通过智能技术生成

1. 前言

想知道JDK中为第三方服务提供的接口的实现类在双亲委托模式加载不了时,是如何进行类加载的吗?

想知道为什么现在几乎不用再在项目中配置数据库驱动类了吗?

那么请认真阅读完该文章,你将得到你想要的答案!!!


2. Java SPI

什么是Java SPI?SPI是Service Provider Interface的简称,也就是服务提供发现机制;Java中的SPI就是一种将服务接口与服务实现分离,以达到松耦合、提高程序可扩展性为目的的编程思想;在JDK中最典型案例的莫过于JDBC模块;

Java SPI的约定:

  • 定义一个接口;
  • 在项目资源的META-INF目录下创建services目录,再在services目录下创建以接口全名为名字的文件,然后在该文件中写上接口实现类的全名,注意文件编码格式必须为UTF-8;
  • JDK中提供了一个ServiceLoader类,可动态的加载通过SPI方式配置的接口的实现类;

3. 手写Java SPI实例

3.1 定义一个Animal接口

在这里插入图片描述

package cn.jackiegu.jvm.study.spi;

/**
 * 动物接口
 *
 * @author JackieGu
 * @date 2021/4/23
 */
public interface Animal {

    void move();
}

3.2 创建两个实现类BirdCat实现Animal接口

在这里插入图片描述

package cn.jackiegu.jvm.study.spi;

/**
 * 鸟
 *
 * @author JackieGu
 * @date 2021/4/23
 */
public class Bird implements Animal {

    @Override
    public void move() {
        System.out.println("小鸟:飞");
    }
}
package cn.jackiegu.jvm.study.spi;

/**
 * 猫咪
 *
 * @author JackieGu
 * @date 2021/4/23
 */
public class Cat implements Animal {

    @Override
    public void move() {
        System.out.println("小猫:跑");
    }
}

3.3 创建Animal接口的配置文件cn.jackiegu.jvm.study.spi.Animal,并配置上接口的实现类全名

在这里插入图片描述

cn.jackiegu.jvm.study.spi.Bird
cn.jackiegu.jvm.study.spi.Cat

3.4 使用ServiceLoader类进行测试

package cn.jackiegu.jvm.study.spi;

import java.util.ServiceLoader;

/**
 * 测试类
 *
 * @author JackieGu
 * @date 2021/4/23
 */
public class SPITest {

    public static void main(String[] args) {
        ServiceLoader<Animal> animals = ServiceLoader.load(Animal.class);
        for (Animal animal : animals) {
            animal.move();
        }
    }
}
// 运行结果
小鸟:飞
小猫:跑

4. Java SPI源码分析

备注:本文源码分析以ServiceLoader.load(Class<S> service)方法作为入口;

4.1 ServiceLoader类介绍

/**
 * ServiceLoader类实现了Iterable接口,表示该类实例可以通过迭代器遍历自身元素
 * ServiceLoader类被final关键字修饰,表示该类不可被继承
 */
public final class ServiceLoader<S> implements Iterable<S> {
    
    /**
     * 提供程序类的配置文件路径前缀
     */
    private static final String PREFIX = "META-INF/services/";
    
    /**
     * 正在加载的服务的类或接口
     */
    private final Class<S> service;
    
    /**
     * 用于定位、加载和实例化提供程序类的类加载器
     */
    private final ClassLoader loader;
    
    /**
     * 上下文权限控制器,一般用不到
     */
    private final AccessControlContext acc;
    
    /**
     * 用于缓存提供者实例
     */
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    
    /**
     * 惰性查找迭代器
     */
    private LazyIterator lookupIterator;
    
    // ......
    
    /**
     * 私有的构造方法
     */
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        // ......
    }
    
    // ......
    
    /**
     * 私有的内部迭代器类,实现完全惰性查找提供程序类
     */
    private class LazyIterator implements Iterator<S> {      
        // ......
    }
    
    // ......
}

4.2 创建ServiceLoader实例

public static <S> ServiceLoader<S> load(Class<S> service) {
    // 获取当前线程类加载器,该类加载器很重要,是定位提供程序类的配置文件和实例提供程序类的重要角色
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    // 调用重载方法
    return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
                                        ClassLoader loader) {
    // 调用私用构造方法创建实例
    return new ServiceLoader<>(service, loader);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
    // 实例service属性,并判断其是否为null
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    // 实例loader属性,为null时使用ApplicationClassLoader
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    // 实例acc属性,默认情况SecurityManager是关闭的,所以acc一般为null
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    reload();
}
public void reload() {
    // 清空缓存
    providers.clear();
    // 实例惰性查找迭代器
    lookupIterator = new LazyIterator(service, loader);
}
private LazyIterator(Class<S> service, ClassLoader loader) {
    this.service = service;
    this.loader = loader;
}

4.3 查找提供程序类,并实例化

// ServiceLoader实现的iterator方法
public Iterator<S> iterator() {
    // 返回一个匿名的Iterator实例
    return new Iterator<S>() {

        // 已知的提供者迭代器,这个属性在ServiceLoader第二次及以上遍历时起作用
        Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();

        /**
         * 是否还有下一个元素
         */
        public boolean hasNext() {
            if (knownProviders.hasNext())
                return true;
            // 若已知的提供者没有,则从惰性查找迭代器去判断
            return lookupIterator.hasNext();
        }

        /**
         * 获取下一个元素
         */
        public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            // // 若已知的提供者没有,则从惰性查找迭代器去获取
            return lookupIterator.next();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    };
}
private class LazyIterator implements Iterator<S> {
    // 正在加载的服务的类或接口
    Class<S> service;
    // 用于定位、加载和实例化提供程序类的类加载器
    ClassLoader loader;
    // 提供程序类配置文件URL实例的枚举
    Enumeration<URL> configs = null;
    // 提供程序类迭代器
    Iterator<String> pending = null;
    // 下一个元素
    String nextName = null;

	// ......
    
    private boolean hasNextService() {
        if (nextName != null) {
            return true;
        }
        // 首次调用时configs为null,则进行加载
        if (configs == null) {
            try {
                // 提供程序类的配置文件完整路径
                String fullName = PREFIX + service.getName();
                if (loader == null)
                    configs = ClassLoader.getSystemResources(fullName);
                else
                    // 通过类加载器去获取配置文件URL实例枚举
                    configs = loader.getResources(fullName);
            } catch (IOException x) {
                fail(service, "Error locating configuration files", x);
            }
        }
        // 首次调用时pending为null
        while ((pending == null) || !pending.hasNext()) {
            // 循环遍历提供程序类的配置文件URL实例枚举,将每一个配置文件URL实例遍历完成后才进行下一个,若没有配置文件URL实例了才返回false
            if (!configs.hasMoreElements()) {
                return false;
            }
            // 解析配置文件,并返回提供程序类迭代器
            pending = parse(service, configs.nextElement());
        }
        // 获取下一个元素,并返回true
        nextName = pending.next();
        return true;
    }

    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
    }

    /**
     * 判断是否还有下一个元素
     */
    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);
        }
    }

    /**
     * 获取下一个元素
     */
    public S next() {
        if (acc == null) {
            // 调用该方法处理
            return nextService();
        } else {
            PrivilegedAction<S> action = new PrivilegedAction<S>() {
                public S run() { return nextService(); }
            };
            return AccessController.doPrivileged(action, acc);
        }
    }

    public void remove() {
        throw new UnsupportedOperationException();
    }  
}

5. Java SPI实战案例

5.1 创建一个自定义数据库驱动类实现JDBC的Driver接口

在这里插入图片描述

package cn.jackiegu.jvm.study.spi;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Properties;
import java.util.logging.Logger;

/**
 * 自定义数据库驱动类
 *
 * @author JackieGu
 */
public class Driver implements java.sql.Driver {

    static {
        try {
            // 注意日志打印信息
            System.out.println("The custom driver is loaded");
            DriverManager.registerDriver(new Driver());
        } catch (SQLException e) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    @Override
    public Connection connect(String url, Properties info) throws SQLException {
        return null;
    }

    @Override
    public boolean acceptsURL(String url) throws SQLException {
        return false;
    }

    @Override
    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
        return new DriverPropertyInfo[0];
    }

    @Override
    public int getMajorVersion() {
        return 0;
    }

    @Override
    public int getMinorVersion() {
        return 0;
    }

    @Override
    public boolean jdbcCompliant() {
        return false;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}

5.2 创建Driver接口的配置文件,并配置上刚刚创建的自定义数据库驱动类

在这里插入图片描述

cn.jackiegu.jvm.study.spi.Driver

5.3 在pom.xml中添加mysql依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.21</version>
</dependency>

5.4 通过DriverManager获取数据库连接

public class SPITest {

    public static void main(String[] args) throws Exception {
        String url = "jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8";
        String username = "root";
        String password = "123456";

        Connection connection = DriverManager.getConnection(url, username, password);
        System.out.println("DriverManager ClassLoader: " + DriverManager.class.getClassLoader());
        System.out.println("Connection ClassLoader: " + Connection.class.getClassLoader());
        System.out.println("Connection Instance ClassLoader: " + connection.getClass().getClassLoader());
        System.out.println("Connection Instance: " + connection);
    }
}
// 运行结果
The custom driver is loaded
DriverManager ClassLoader: null
Connection ClassLoader: null
Connection Instance ClassLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
Connection Instance: com.mysql.cj.jdbc.ConnectionImpl@38082d64

运行结果分析:

  • The custom driver is loaded表示自定义数据库驱动类被加载;
  • DriverManagerConnection是基础类,被Bootstrap ClassLoader加载是正常情况;
  • Connection的实例为com.mysql.cj.jdbc.ConnectionImpl,但其加载器却为Application ClassLoader;

5.5 重点源码分析

/**
 * DriverManager源码分析
 */
public class DriverManager {
    
    // ......
    
    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;
        }
        
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
				// 加载Driver接口的实现类
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                try{
                    while(driversIterator.hasNext()) {
                        /*
                         * 获取Driver接口的实现类,这里会引起驱动实现类的静态块代码被执行,也就是运行结果分析第一点的原因
                         * ServiceLoader中加载实现类是通过当前线程的类加载器进行的,也就是运行结果分析第三点的原因
                         */
                        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);
            }
        }
    }
    
}

在mysql驱动jar包中同样存在一个Driver接口的配置文件

在这里插入图片描述

其内容如下:

com.mysql.cj.jdbc.Driver
spackage com.mysql.cj.jdbc;

import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * mysql驱动类源码分析
 */
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!");
        }
    }
}
public class DriverManager {
    
    /**
     * 注册的JDBC驱动程序集合
     */
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    
    // ......
    
    public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException {
        registerDriver(driver, null);
    }
    
    public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException {
        if(driver != null) {
            // 如果该驱动程序未被添加过,则添加到注册驱动程序中
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
            throw new NullPointerException();
        }
        println("registerDriver: " + driver);
    }
    
    // ......
    
    @CallerSensitive
    public static Connection getConnection(String url, String user, String password) throws SQLException {
        java.util.Properties info = new java.util.Properties();

        if (user != null) {
            info.put("user", user);
        }
        if (password != null) {
            info.put("password", password);
        }
        return (getConnection(url, info, Reflection.getCallerClass()));
    }
    
    // ......
    
    private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        // 获取调用类的类加载器,若调用类为null,则使用当前线程的类加载器
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }

        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }

        println("DriverManager.getConnection(\"" + url + "\")");

        SQLException reason = null;

        // 遍历注册的JDBC驱动程序集合
        for(DriverInfo aDriver : registeredDrivers) {
            // 判断JDBC驱动程序的类加载器与当前的是否一致,当一致时才进行数据库连接申请
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // 当数据库连接不为null时表示申请成功,进行返回
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        // 发生异常时记录异常
                        reason = ex;
                    }
                }
            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }
        }

        // 若程序执行到这里,则已经表示申请数据库连接失败了
        if (reason != null)    {
            println("getConnection failed: " + reason);
            // 若记录的异常不为null,则抛出该异常
            throw reason;
        }

        println("getConnection: no suitable driver found for "+ url);
        // 若记录的异常为null,则表示没有找到合适的数据库驱动类
        throw new SQLException("No suitable driver found for "+ url, "08001");
    }
    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

JackieGGu

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

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

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

打赏作者

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

抵扣说明:

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

余额充值