jdbc 驱动加载方式 --》java spi service provider interface 服务机制

这几天在看java 类加载机制,看到 spi 服务机制破坏了双亲委派模型,特地研究了下典型的 spi 服务 jdbc 驱动 
首先运行一下代码,查看 mysql jdbc 驱动的类加载(maven 项目已经引进 jdbc 驱动依赖,版本为5.1.41)

public static void main(String[] args)
    {
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        Driver driver;
        while (drivers.hasMoreElements())
        {
            driver = drivers.nextElement();
            System.out.println(driver.getClass() + "------" + driver.getClass().getClassLoader());
        }
        System.out.println(DriverManager.class.getClassLoader());
    }

输出结果如下:

class com.mysql.jdbc.Driver------sun.misc.Launcher$AppClassLoader@2a139a55
class com.mysql.fabric.jdbc.FabricMySQLDriver------sun.misc.Launcher$AppClassLoader@2a139a55
null

可以看到代码中并没有调用 Class.forName(“”)的代码,但DriverManager中已经加载了两个 jdbc 驱动,而却这两个驱动都是使用的应用类加载器(AppClassLoader)加载的,而DriverManager本身的类加载器确是 null 即BootstrapClassLoader,按照双亲委派模型的规则,委派链如下: 
SystemApp class loader -> Extension class loader -> Bootstrap class loader 
,父加载器BootstrapClassLoader是无法找到AppClassLoader加载的类的,此时使用了线程上下文加载器,Thread.currentThread().setContextClassLoader()可以将委派链左边的类加载器,设置为线程上下文加载器,此时右边的加载器就可以使用线程上下文加载器委托子加载器加载类

可以查看DriverManager的源码

/**
     * 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;
        }
sun.misc.Providers()
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                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);
            }
        }
    }

可以看到DriverManager在初始化时会使用ServiceLoader来加载java.sql.Driver的实现类,此处就是 spi 服务的思想 
查看 ServiceLoader 的load 代码

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 = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

创建了一个ServiceLoader,使用 reload 方法来加载,ServiceLoader 的主要参数与 reload 的代码如下:

private static final String PREFIX = "META-INF/services/";
public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

LazyIterator是一个懒加载的迭代器,看一下这个迭代器的实现:

 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;
        }

        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;
        }

        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();
        }

    }

回头查看DriverManager的初始化代码,可以看到如下代码:

 while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
1
2
3
可以看出DriverManager会循环调用所有在META-INF/services/java.sql.Driver下定义了所有类的 Class.forName()方法 
那么这些加载的驱动是如何被注册在DriverManager中的?我们看 mysql 的驱动 Driver 的实现类 可以看到 Driver的实现在初始化时就进行了注册,代码如下:

static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

这段代码即可将 java.sql.Driver 的实现类注册进DriverManager,注意此段代码中 new Driver()是com.mysql.jdbc.Driver不错看成java.sql.Driver 
最后查看下实现 spi 服务必不可少的文件 META-INF/services/java.sql.Driver(这个特定用来实现 java.sql.Driver 的接口的 spi 服务)这个文件中内容如下:

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

可以看到这两个类即为文章开头实验的那两个 jdbc 驱动

注意并不是所有版本的 jdbc 驱动都实现了 spi 服务,应该是5.1.5及之后的版本才实现了这种服务,之前的版本还是需要手动调用 Class.forName 方法来加载驱动,还有好像 ojdbc 的驱动均没有实现 spi 服务

搞清楚了 spi 服务于 DriverManager 加载的过程,我们可以自己尝试实现一个简单的 jdbc 驱动(仅仅实现了类加载的部分) 
使用 maven 工程,新建类com.lcy.mysql.Driver

public class Driver implements java.sql.Driver
{

    static
    {
        try
        {
            DriverManager.registerDriver(new com.lcy.mysql.Driver());
        }
        catch (SQLException e)
        {
            throw new RuntimeException("register driver fail");
        }
    }

    @Override
    public Connection connect(String url, Properties info)
        throws SQLException
    {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public boolean acceptsURL(String url)
        throws SQLException
    {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info)
        throws SQLException
    {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int getMajorVersion()
    {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public int getMinorVersion()
    {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public boolean jdbcCompliant()
    {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public Logger getParentLogger()
        throws SQLFeatureNotSupportedException
    {
        // TODO Auto-generated method stub
        return null;
    }

}

仅仅写了一个初始化方法,其他方法均使用默认空实现,在 src/mian/resources 目录下新建文件 /META-INF/services/java.sql.Driver 填入内容com.lcy.mysql.Driver 打包发布 
在之前的文章开始的测试工程中引入工程依赖(如果是同一工程,直接运行即可),运行可以看到结果如下:

class com.mysql.jdbc.Driver------sun.misc.Launcher$AppClassLoader@2a139a55
class com.mysql.fabric.jdbc.FabricMySQLDriver------sun.misc.Launcher$AppClassLoader@2a139a55
class com.lcy.mysql.Driver------sun.misc.Launcher$AppClassLoader@2a139a55
null

可以看到,已经加载了我们自定义的com.lcy.mysql.Driver(虽然这个加载器没有实现任何功能,但测试 spi 机制的目的已经实现)
 

以上可以看出,spi 服务机制 就是自定义一套接口,然后让子类实现,

并在classpath下的META-INF/services/目录下以接口全路径名定义文件,文件内容为 实现类全名称

java 定义了jdbc 规范也不例外,各厂商实现的服务里也添加了配置文件,

同时 jdbc 规范的DriverManager 类中 也用 serviceloader 主动去加载了 各个配置文件内的驱动。

猜你喜欢

转载自blog.csdn.net/xiaoliuliu2050/article/details/87798470