ServiceLoader和DriverManager的前世今生

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ServiceLoader和DriverManager的前世今生相关的知识,希望对你有一定的参考价值。

ServiceLoader和DriverManager的前世今身


JDBC获取数据库连接的方式

我们先来看看JDBC获取数据库连接有哪几种做法:

  • 直接实例化出特定的驱动,不经过DriverManager,这样的好处是方便,坏处是需要提前知道存在哪些驱动可以使用,因此该方式不推荐。
        Driver driver=new com.mysql.jdbc.Driver();
        String url="jdbc:mysql://localhost:3306/test1";
        Properties info=new Properties();
        info.setProperty("user","root");
        info.setProperty("password","xxx");
        Connection conn=driver.connect(url,info);
  • 和第一种方式没有区别,这里不再多说
       Class clazz=Class.forName("com.mysql.jdbc.Driver");
       Driver driver=(Driver)clazz.newInstance();
       String url="jdbc:mysql://localhost:3306/test1";
        Properties   info=new Properties();
        info.setProperty("user","root");
        info.setProperty("password","xxx");
        Connection conn=driver.connect(url,info);
  • 下面就是把Driver注册到了DriverManager进行管理,但是也不推荐这种方式
        String url="jdbc:mysql://localhost:3306/test1";
        String user="root";
        String password="xxx";
        Class clazz=Class.forName("com.mysql.jdbc.Driver");
        Driver driver=(Driver)clazz.newInstance();
        //注册驱动
        DriverManager.registerDriver(driver);
        //获取连接
        Connection conn=DriverManager.getConnection(url,user,password);
  • 下面这种方式相对来说就舒服很多,但是其实还可以更简单
        String url="jdbc:mysql://localhost:3306/test1";
        String user="root";
        String password="xxx";
        //加载Driver
       Class clazz=Class.forName("com.mysql.jdbc.Driver");
        //获取连接
        Connection conn=DriverManager.getConnection(url,user,password);
  • 下面一行代码就够了
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test1",
                "root", "xxx");

相信大家看着我一步步简化到最后,已经蒙了,为什么可以这样写,别急,下面我们就来看看DriverManager到底是怎么实现的


ServiceLoader

因为DriverManager实现主要依靠了ServiceLoader来完成,因此这里先来看看ServiceLoader干了什么:

在Java应用中存在着很多服务提供者接口(Service Provider Interface,SPI),这些接口允许第三方为它们提供实现,如常见的 SPI 有 JDBC、JNDI等,这些 SPI 的接口属于 Java 核心库,一般存在rt.jar包中,由Bootstrap类加载器加载,而 SPI 的第三方实现代码则是作为Java应用所依赖的 jar 包被存放在classpath路径下,由于SPI接口中的代码经常需要加载具体的第三方实现类并调用其相关方法,但SPI的核心接口类是由引导类加载器来加载的,而Bootstrap类加载器无法直接加载SPI的实现类,同时由于双亲委派模式的存在,Bootstrap类加载器也无法反向委托AppClassLoader加载器SPI的实现类。在这种情况下,我们就需要一种特殊的类加载器来加载第三方的类库,而线程上下文类加载器就是很好的选择。

线程上下文类加载器(contextClassLoader)是从 JDK 1.2 开始引入的,我们可以通过java.lang.Thread类中的getContextClassLoader()和 setContextClassLoader(ClassLoader cl)方法来获取和设置线程的上下文类加载器。

如果没有手动设置上下文类加载器,线程将继承其父线程的上下文类加载器,初始线程的上下文类加载器是系统类加载器(AppClassLoader),在线程中运行的代码可以通过此类加载器来加载类和资源,如下图所示,以jdbc.jar加载为例

从图可知rt.jar核心包是有Bootstrap类加载器加载的,其内包含SPI核心接口类,由于SPI中的类经常需要调用外部实现类的方法,而jdbc.jar包含外部实现类(jdbc.jar存在于classpath路径)无法通过Bootstrap类加载器加载,因此只能委派线程上下文类加载器把jdbc.jar中的实现类加载到内存以便SPI相关类使用。显然这种线程上下文类加载器的加载方式破坏了“双亲委派模型”,它在执行过程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器,当然这也使得Java类加载器变得更加灵活。

例如我们这里要介绍的DriverManager,它是Java核心rt.jar包中的类,该类用来管理不同数据库的实现驱动即Driver,它们都实现了Java核心包中的java.sql.Driver接口,如mysql驱动包中的com.mysql.jdbc.Driver。

而因为DriverManager是由启动类加载器进行加载的,启动类加载器无法去加载类路径下的Driver接口实现类,因此需要将加载这些实现类的需求委托给线程上下文类加载器来完成,实际是通过ServiceLoader调用线程上下文类加载器去加载这些接口实现类的。

下面就来看看ServiceLoader是如何加载的吧。


源码分析

该类中有一个静态变量,指明了ServiceLoader会去哪里寻找需要被加载的类:

//会去每个类路径下的META-INF/services/包下,寻找需要被加载的类
   private static final String PREFIX = "META-INF/services/";

还有其他一些属性如下:

    //需要被加载的类,这里给出的是接口,然后在类路径下寻找其实现类
    private final Class<S> service;

    //用来寻找和加载实现类的类加载器,即为线程上下文类加载器
    private final ClassLoader loader;

    //存放所找到的所有可用的实现类
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // 用来遍历上面这个集合的迭代器,当然没那么简单,它还会负责类的加载
    private LazyIterator lookupIterator;

下面来看看其中的一些方法,首先是关于如何获取一个ServiceLoader实例的方法:

//重新加载ServiceLoader
    public void reload() 
    //清空集合
        providers.clear();
        lookupIterator = new LazyIterator(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呢?

    //我们一般调用该方法来加载某个接口提供的所有实现类
    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)
    
       //返回一个 ServiceLoader实例,当然这里不算是单例模式
        return new ServiceLoader<>(service, loader);
    

ServiceLoader的创建过程分析完了,下面来分析一下它是如何定位到那些实现类并进行初始化的吧:

一般都是调用它的iterator()方法返回一个LazyIterator迭代器:

    public Iterator<S> iterator() 
        return new Iterator<S>() 

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

        ;
    

下面看看LazyIterator是怎么实现的吧:

        public boolean hasNext() 
          ...
          //核心方法
                return hasNextService();
         ....
        
        
        public S next() 
         ...
        //核心方法
                return nextService();
        ...
        

核心方法

private boolean hasNextService() 
            if (nextName != null) 
                return true;
            
            if (configs == null) 
                try 
                //PREFIX就是"META-INF/services/"
                //service.getName()就是接口全类名,如果是Drive接口
                //那么就是去类路径下寻找所有的
                //META-INF/services/java.sql.Driver
                    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
        

小结

ServiceLoader核心思路去是去类路径寻找META-INF/services/接口全类名这样一个文件,该文件里面记录了实现类的全类名,拿到实现类的全类名后,再去实例化实现类,然后返回所有实现类集合。

            //只有上面实例化没出错,这里才会创建实例,然后返回
            //在这里类会被初始化---注意这里---对应的实现类被初始化
            //那么对应的实现类内部的静态代码块会被执行--一行有很大作用
                S p = service.cast(c.newInstance());

实例化过程中,对应的实现类的静态代码块会被调用,因此我们可以在实现类的静态代码块中做些手脚,而DriverManager实际上就是靠这个手脚,完成实现类到DriverManager的注册的,下面我们会看到。


DriverManager

DriverManager中用一个List集合记录了所有注册到自己身上的驱动类:

    // List of registered JDBC drivers
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

DriverManager类中有一个静态代码块:

    static 
       //加载并初始化所有驱动类,就是靠上面的ServiceLoader完成的
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    

loadInitialDrivers–加载并初始化所有驱动类

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按照SPI规范去寻找所有可能存在的驱动实现类
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try
                //hasNext实际调用hasNextService-去类路径下
                //寻找所有的META-INF/services/java.sql.Driver文件
                    while(driversIterator.hasNext()) 
                    //next实际调用nextService
                    //该方法会实例化实现类
                        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);
                //初始化这些实现类,第二个参数为true,表示会执行初始化过程
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
             catch (Exception ex) 
                println("DriverManager.Initialize: load failed: " + ex);
            
        
    

loadInitialDrivers方法执行过后,registeredDrivers 集合中就已经有了所有能找到的驱动实现类集合,
但是上面都没看到有往集合中添加驱动的代码,怎么集合就有元素了呢?

那是因为驱动实现类的静态代码块会在初始化的时候被调用,然后往registeredDrivers注册自己。


registerDriver—注册驱动

registerDriver就是用来将驱动实现类放入DriverManager的registeredDrivers集合中的

    public static synchronized void registerDriver(java.sql.Driver driver,
            DriverAction da)
        throws SQLException 

        /* Register the driver if it has not already been added to our list */
        if(driver != null) 
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
         else 
            // This is for compatibility with the original DriverManager
            throw new NullPointerException();
        

        println("registerDriver: " + driver);

    

该方法会在实现类的静态代码块中被调用,例如:

package com.mysql.jdbc;

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

public class Driver extends NonRegisteringDriver implements java.sql.Driver 
    public Driver() throws SQLException 
    

    static 
        try 
        //当Mysql的驱动被初始化时,静态代码块会执行,然后就会向DriverManager注册自己
            DriverManager.registerDriver(new Driver());
         catch (SQLException var1) 
            throw new RuntimeException("Can't register driver!");
        
    


getConnection—获取数据库连接

    ServiceLoader的使用

Java之ServiceLoader

浅析JDK中ServiceLoader的源码

java浅析JDK中ServiceLoader的源码

ServiceLoader的使用

ServiceLoader