jvm 类加载机制 之线程上下文类加载器在spi 中的应用 (4)

线程上下文类加载器(context class loader)是从JDK 1.2开始引入的。

类 java.lang.Thread中的方法getContextClassLoader()和setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。

为了加载类,Java提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。那类加载就会存在问题:SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类一般是由系统类加载器来加载的。引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库,正常来说,在java 核心库里的代码都是需要用引导类加载器 来加载的,但是 有个例外class.forName("子类实现"),引导类加载器执行这句代码的时候,必然加载子类实现类,但是它加载不了,怎么办,只能通过线程上下文类加载器,于是代码就改成了 class.forName("子类实现",false,threadcontextloader).这样就可以了。

在 SPI 接口的代码中使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类

我们拿jdbc为例子分析:

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

        //获取spi 机制定义的驱动
        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 主动去加载了 各个配置文件内的驱动。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值