文章 三月 30, 2021

java spi 机制

文章字数 6.1k 阅读约需 6 mins. 阅读次数 0

java spi 机制

​ SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制, 比如有个接口,想运行时动态的给它添加实现,你只需要添加一个实现。我们经常遇到的就是java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,mysql和postgresql都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。

SPI

如上图所示,接口对应的抽象SPI接口;实现方实现SPI接口;调用方依赖SPI接口。

SPI接口的定义在调用方,在概念上更依赖调用方;组织上位于调用方所在的包中,实现位于独立的包中。

用途:

数据库DriverManager、Spring、ConfigurableBeanFactory等都用到了SPI机制.

这里以数据库DriverManager为例

  1. Driver实现(Driver)

    驱动的类的静态代码块中,调用DriverManager的注册驱动方法, new一个自己当参数传给驱动管理器。

    
    package com.mysql.cj.jdbc;
    
    import java.sql.SQLException;
    
    public class Driver extends NonRegisteringDriver implements java.sql.Driver {
        // Register ourselves with the DriverManager
        static {
            try {
                java.sql.DriverManager.registerDriver(new Driver());
            } catch (SQLException E) {
                throw new RuntimeException("Can't register driver!");
            }
        }
        public Driver() throws SQLException {
            // Required for Class.forName().newInstance()
        }
    }
    
  2. Mysql DriverManager实现(DriverManager)

    内部的静态代码块中有一个loadInitialDrivers方法,

    方法中用到了查找服务实现的工具类ServiceLoader.

    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;
            }
            // 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);
                }
            }
        }
    }
    
  3. SPI机制查找驱动(ServiceLoader)

    可以看到加载META-INF/services/ 文件夹下类名为文件名(这里相当于Driver.class.getName())的资源,然后将其加载到虚拟机, 加载SPI扫描到的驱动来触发他们的初始化。即触发他们的static代码块.

    public final class ServiceLoader<S>
        implements Iterable<S>
    {
    
            private static final String PREFIX = "META-INF/services/";
        
        ......
            private class LazyIterator  implements Iterator<S> {     
                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;
                }    
            }
    }
    
0%