SPI 原理

JDK版本为1.8

SPI

SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。

Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。

系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。
Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以SPI的核心思想就是解耦。

使用介绍

要使用Java SPI,需要遵循如下约定:

1、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
2、接口实现类所在的jar包放在主程序的classpath中;
3、主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
4、SPI的实现类必须携带一个不带参数的构造方法;
项目结构如下:

src/main
--------java
        lzc.spidemo
            api
                Car.java
            impl
                BCCar.java
                BMCar.java
             Test.java   
--------resource
        META-INF.services
            lzc.spidemo.api.Car

Car.java

public interface Car {
    public String getCarName();
}

BCCar.java

public class BCCar implements Car {
    @Override
    public String getCarName() {
        return "奔驰车";
    }
}

BMCar.java

public class BMCar implements Car {
    @Override
    public String getCarName() {
        return "宝马车";
    }
}

META-INF/services/lzc.spidemo.api.Car

META-INF/services/下新建一个文件,名字为lzc.spidemo.api.Car

lzc.spidemo.impl.BCCar
lzc.spidemo.impl.BMCar

Test.java

public class Test {
    public static void main(String[] args) {
        // ServiceLoader.load(Car.class)
        ServiceLoader<Car> carList = ServiceLoader.load(Car.class);
        // ServiceLoader.iterator()
        Iterator<Car> carIterator = carList.iterator();
        while (carIterator.hasNext()) { // LazyIterator.hasNext()
            Car car = carIterator.next(); // LazyIterator.next()
            System.out.println(car.getCarName());
        }
    }
}

根据运行结果可以发现,ServiceLoader.load(Car.class)可以帮我们找到Car的实现类BCCarBMCar,接下来通过原来来分析一下它是如何找到Car的实现类的。

源码分析

Java的SPI机制实现跟ServiceLoader这个类有关

public final class ServiceLoader<S> implements Iterable<S> {
    // 读取配置文件的前缀路径 META-INF/services/
    private static final String PREFIX = "META-INF/services/";
    // 需要被加载的服务接口或者服务类
    private final Class<S> service;
    // 类加载器
    private final ClassLoader loader;
    // 创建ServiceLoader时采用的访问控制上下文,默认情况下为 null
    private final AccessControlContext acc;
    // 缓存SPI的实现,key是完整类名
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    // 当前的迭代器,默认初始化为: new LazyIterator(service, loader)
    // 这里是懒加载的,只有使用的时候才去迭代,加载
    // LazyIterator 是 ServiceLoader 的内部类
    private LazyIterator lookupIterator;
}

可以看到,ServiceLoader实现了Iterable接口,覆写其iterator方法能产生一个迭代器;同时ServiceLoader有一个内部类LazyIterator,而LazyIterator又实现了Iterator接口,说明LazyIterator是一个迭代器。

ServiceLoader.load(Car.class)

ServiceLoader.load(Car.class)开始分析

// java.util.ServiceLoader
public static <S> ServiceLoader<S> load(Class<S> service) {
    // 获取当前线程上下文类加载器 AppClassLoader
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    // 把刚才取出的线程上下文类加载器作为参数传入,用于后面去加载classpath中的外部厂商提供的驱动类
    // 将service接口类和线程上下文类加载器作为参数传入,继续调用load方法
    return ServiceLoader.load(service, cl);
}

查看ServiceLoader.load(service, cl)

// java.util.ServiceLoader
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
    // 将service接口类和线程上下文类加载器作为构造参数,创建一个ServiceLoader对象
    return new ServiceLoader<>(service, loader);
}

查看new ServiceLoader<>(service, loader)

// java.util.ServiceLoader
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();
}

这里主要是对类属性做赋值操作,然后调用了reload()方法

查看reload()方法

// java.util.ServiceLoader
public void reload() {
    // 清除缓存
    providers.clear();
    // 新建 LazyIterator
    lookupIterator = new LazyIterator(service, loader);
}

reload方法中又新建了一个LazyIterator对象,然后赋值给lookupIterator

// java.util.ServiceLoader
// LazyIterator 是 ServiceLoader的内部类
private class LazyIterator implements Iterator<S> {
    Class<S> service;
    ClassLoader loader;
    Enumeration<URL> configs = null;
    Iterator<String> pending = null;
    String nextName = null;

    private LazyIterator(Class<S> service, ClassLoader loader) {
        this.service = service;
        this.loader = loader;
    }
}

在构建LazyIterator对象时,只是给其serviceloader属性赋值,并没有去加载接口的实现类。

ServiceLoader.iterator()

查看ServiceLoader.iterator()

// java.util.ServiceLoader
public Iterator<S> iterator() {
    // 这里返回的是一个匿名的迭代器对象
    return new Iterator<S>() {
        // 将 providers 缓存的数据转换成迭代器 Iterator 并赋值给 knownProviders
        Iterator<Map.Entry<String,S>> knownProviders
            = providers.entrySet().iterator();

        public boolean hasNext() {
            // 如果缓存中有数据就直接返回true
            if (knownProviders.hasNext())
                return true;
            // 缓存中没有数据,调用 LazyIterator.hasNext()
            return lookupIterator.hasNext();
        }

        public S next() {
            // 如果缓存中有数据就直接从缓存中返回
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            // 缓存中没有数据,调用 LazyIterator.next()
            return lookupIterator.next();
        }

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

    };
}

LazyIterator.hasNext()

查看LazyIterator.hasNext()

// java.util.ServiceLoader.LazyIterator
// LazyIterator 是 ServiceLoader 的内部类
public boolean hasNext() {
    // 默认情况下 acc = null
    if (acc == null) {
        return hasNextService();
    } else {
        PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
            public Boolean run() { return hasNextService(); }
        };
        return AccessController.doPrivileged(action, acc);
    }
}

查看hasNextService()方法

// java.util.ServiceLoader.LazyIterator
// LazyIterator 是 ServiceLoader 的内部类
// 这里会涉及到一个名词 "全限定名"
// 这里说的 "全限定名" = "包路径"."类名名称或者是接口名名称"
private boolean hasNextService() {
    // 如果 nextName 不为空,则直接返回 true
    if (nextName != null) {
        return true;
    }
    if (configs == null) {
        try {
            // PREFIX = "META-INF/services/"
            // service.getName() 获取接口的全限定名
            // 在构建LazyIterator对象时已经给其成员属性service赋值
            String fullName = PREFIX + service.getName();

            // 在构建LazyIterator对象时已经给其成员属性loader赋值
            // 加载 "META-INF/services/接口的全限定名" 文件
            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;
        }
        // 解析 "META-INF/services/接口的全限定名" 文件内容
        // 返回 "META-INF/services/接口的全限定名" 文件内容中的服务提供者的全限定名并赋值给pending属性
        pending = parse(service, configs.nextElement());
    }
    // 然后取出一个服务提供者全限定名赋值给LazyIterator的成员变量nextName
    nextName = pending.next();
    return true;
}

这里用到了懒加载的思想,当需要时才去加载配置文件。

该方法的主要功能是:去META-INF/services/目录下加载接口文件的内容,解析文件内容赋值给LazyIterator的成员变量pendingpending是一个Iterator,然后取出第一个值赋值给LazyIterator的成员变量nextName

比如META-INF/services/lzc.spidemo.api.Car的文件内容为。

lzc.spidemo.impl.BCCar
lzc.spidemo.impl.BMCar

那么LazyIteratorpending属性就保存了lzc.spidemo.impl.BCCarlzc.spidemo.impl.BMCar这两个值,然后取出lzc.spidemo.impl.BCCar赋值给LazyIterator的成员变量nextName

执行完LazyIteratorhasNext方法后,会继续执行LazyIteratornext方法

LazyIterator.next()

查看LazyIterator.next()方法

// java.util.ServiceLoader.LazyIterator
// LazyIterator 是 ServiceLoader 的内部类
public S next() {
    // 默认情况下 acc = null
    if (acc == null) {
        return nextService();
    } else {
        PrivilegedAction<S> action = new PrivilegedAction<S>() {
            public S run() { return nextService(); }
        };
        return AccessController.doPrivileged(action, acc);
    }
}

查看LazyIterator.nextService()方法

// java.util.ServiceLoader.LazyIterator
// LazyIterator 是 ServiceLoader 的内部类
private S nextService() {
    if (!hasNextService())
        throw new NoSuchElementException();
    // hasNextService()方法中为 nextNam e赋值过服务提供者实现类的全限定名
    String cn = nextName;
    // 将 nextName 设置为空
    // 下次调用 hasNextService() 时会从 pending 获取值并赋值给 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集合进行缓存
        providers.put(cn, p);
        // 返回实例
        return p;
    } catch (Throwable x) {
        fail(service,
             "Provider " + cn + " could not be instantiated",
             x);
    }
    throw new Error();          // This cannot happen
}

LazyIterator.nextService()利用Class.forName(String name, boolean initialize,ClassLoader loader)加载服务提供者实现类,然后利用c.newInstance()实例化,把实例化的类存储到providers中缓存起来。

JDBC驱动加载

JDBC驱动加载是利用了Java的SPI机制。JDBC提供了一组接口规范,不同的数据库厂商只要编写符合这套JDBC接口规范的驱动代码,那么就可以用Java语言来连接数据库。

以MySQL为例分析JDBC驱动加载源码。

首先需要引入mysql-connector-java依赖包,版本为8.0.20。mysql-connector-java.jar包目录下的META-INF/services/下有一个java.sql.Driver文件,文件内容如下所示:

com.mysql.cj.jdbc.Driver

加载MySQL驱动类

public class DBUtils {
    private static String dirverClassName = "com.mysql.cj.jdbc.Driver";
    private static String url = "jdbc:mysql://127.0.0.1:3306/lzc?characterEncoding=utf8";
    private static String user = "root";
    private static String password = "root";
    public static Connection getDBConnection() {
        Connection conn = null;
//        try {
//            // 在JDBC 4.0 规范中可以省略这一步了  
//            // Class.forName 用来加载类信息并执行类的静态块
//            Class.forName(dirverClassName);
//        } catch (ClassNotFoundException e) {
//            e.printStackTrace();
//        }
        try {
            // 通过 DriverManager 获取 Connection
            conn = DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }
}

从上面的代码可以发现,通过DriverManager.getConnection(String url,String user, String password)可以获取Connection连接信息,此时肯定会执行DriverManager的静态块代码。

查看java.sql.DriverManager静态代码:

// java.sql.DriverManager
public class DriverManager {
    // 用来保存JDBC驱动列表
    // 就是实现了 java.sql.Driver 接口的类在类加载时会主动注册到 registeredDrivers
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
}

查看loadInitialDrivers()方法

// java.sql.DriverManager
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() {
            // 前面已经分析过这里的原理了
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            // 调用 ServiceLoader 的 iterator 方法
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
            try{
                // 在迭代的时候会去加载META-INF/services/java.sql.Driver文件,
                // 文件内容为 com.mysql.cj.jdbc.Driver
                // 然后实例化 com.mysql.cj.jdbc.Driver
                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);
        }
    }
}

这里主要是利用了Java的SPI机制去实例化MySQL驱动类,下面查看MySQL驱动类是如何注册到DriverManager类的registeredDrivers集合中

驱动类注册到DriverManager

查看com.mysql.cj.jdbc.Driver的静态块代码

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            // 这里会将 com.mysql.cj.jdbc.Driver 实例注册到
            // DriverManager类的registeredDrivers集合中
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

看到这里可以知道,com.mysql.cj.jdbc.Driver在实例化的时候会执行它的静态块代码,此时会将自己注册到DriverManager类的registeredDrivers集合中。

MySQL驱动连接数据库

com.mysql.cj.jdbc.Driver已经注册到DriverManager类的registeredDrivers集合中,下面查看它是何时被调用的。

通过前面的例子可以知道,通过DriverManager.getConnection(String url,String user, String password)可以获取Connection连接信息,查看这个方法的代码:

// java.sql.DriverManager
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()));
}

继续往下看

// java.sql.DriverManager
private static Connection getConnection(
    String url, java.util.Properties info, Class<?> caller) throws SQLException {
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
        // synchronize loading of the correct classloader.
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }
    if(url == null) {
        throw new SQLException("The url cannot be null", "08001");
    }
    println("DriverManager.getConnection(\"" + url + "\")");
    // Walk through the loaded registeredDrivers attempting to make a connection.
    // Remember the first exception that gets raised so we can reraise it.
    SQLException reason = null;

    // 主要查看这里
    // 遍历 registeredDrivers 集合
    for(DriverInfo aDriver : registeredDrivers) {
        // If the caller does not have permission to load the driver then
        // skip it.
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                println("    trying " + aDriver.driver.getClass().getName());
                // 利用驱动类来连接数据库
                Connection con = aDriver.driver.connect(url, info);
                // 只要连接上就直接放回 Connection,如果有多个驱动类,其余的就会被忽略
                if (con != null) {
                    // Success!
                    println("getConnection returning " + aDriver.driver.getClass().getName());
                    return (con);
                }
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }
        } else {
            println("    skipping: " + aDriver.getClass().getName());
        }

    }

    // if we got here nobody could connect.
    if (reason != null)    {
        println("getConnection failed: " + reason);
        throw reason;
    }
    println("getConnection: no suitable driver found for "+ url);
    throw new SQLException("No suitable driver found for "+ url, "08001");
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!