SPI-服务发现机制

268 阅读4分钟

SPI--服务发现机制

一段JDBC代码引发的思考

下面是一段很普通的JDBC使用代码:

@Test
public void testJDBC() throws SQLException, ClassNotFoundException {
    String url = "jdbc:mysql://localhost:3307/mls";
    String userName = "root";
    String password = "123456";
    // 1 加载驱动
    // Class.forName("com.mysql.cj.jdbc.Driver"); 
    // 2 创建连接
    Connection con = DriverManager.getConnection(url, userName, password);
    // 3 创建语句
    Statement statement = con.createStatement();
    String sql = "select * from mlsdb where id=1";
    // 4 执行sql,获取结果
    ResultSet rs = statement.executeQuery(sql);
    while (rs.next()) {
        System.out.println(rs.getString("province"));
    }
}

其中第一步 Class.forName("com.mysql.cj.jdbc.Driver")通过全限定类名com.mysql.cj.jdbc.Driver,触发了类加载过程,而com.mysql.cj.jdbc.Driver类的源码如下:

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!");
        }
    }
}

可以看到该类包含一个静态代码块static {}, 这部分代码会被编译器收集到class文件的<clinit>()方法中,并在类加载的初始化阶段执行。执行的结果就是将mysql驱动注册到DriverManager

细心的读者会发现Class.forName("com.mysql.cj.jdbc.Driver");这一行被注释掉了,那是不是意味着这段代码是非必须的? 如果是非必须的,那步骤2 中DriverManager是如何使用mysql的驱动去创建的连接呢?再深入的思考下,mysql的驱动是何时被偷偷摸摸的注册到DriverManager中的呢?带着这些问题,通过代码我们来一点点分析本文的主人公--SPI(Service Provider Interface 服务发现机制)

源码分析

DriverManager是用于管理JDBC驱动的基础服务类,位于Java.sql包中,因此是由Bootstrap ClassLoader来进行加载。加载该类时,会执行如下代码块:

    static {
        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对象,并注入线程上下文类加载器和Driver.class
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                //获得迭代器
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try{
                    while(/*查找实现了Driver接口的类*/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);
            }
        }
    }
  • ServiceLoader.load(Driver.class)此方法会实例化一个ServiceLoader对象,并且向其注入线程上下文类加载器和Driver.class;
  • loadedDrivers.iterator():获得ServiceLoader对象的迭代器;
  • driversIterator.hasNext():查找Driver类;
  • driversIterator.next():在实现的“next()”方法中进行类加载,使用上面的线程上下文类加载器。

ServiceLoader.load(Driver.class)

相关调用方法如下:

    // 通过当前线程上下文的类加载器ClassLoader 创建一个执行类型 service 的 服务加载器 ServiceLoader
    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

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

    // 1 清空缓存
    // 2 创建一个懒加载Iterator, 后续调用该Iterator的hasNext()和next()方法才会触发真正的查找和实例化 ServiceProvider提供的服务
    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

loadedDrivers.iterator()

相关方法如下:

    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;
                // 发现服务 (LazyIterator.hasNextService())
                return lookupIterator.hasNext();
            }
            public S next() {
                if (knownProviders.hasNext()) return knownProviders.next().getValue();
                // 加载 该服务类 (LazyIterator.nextService())
                return lookupIterator.next();
            }
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }

LazyIterator--懒加载迭代器

该类是ServiceLoader的内部类

     // service provider 需要将配置文件放到 约定的目录下
     private static final String PREFIX = "META-INF/services/";	

     // 用于 查找和实例化 ServiceProvider提供的服务 的 懒加载内部类
     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 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 {
                //使用loader来进行类加载
                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类中一系列操作之后(包括服务查找和类加载),位于mysql驱动包中的Driver类会被初始化。该类如下所示

该类定义如下:

绕了一圈我们又回到了文章最开始的地方,静态代码块会在com.mysql.cj.jdbc.Driver类加载的实例化阶段被执行,从而将驱动注册到DriverManager中。

总结

JDK6里面引进的一个新的特性ServiceLoader,从官方的文档来说,它主要是用来装载一系列的service provider。而且ServiceLoader可以通过service provider的配置文件来装载指定的service provider。当服务的提供者,提供了服务接口的一种实现之后,我们只需要在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。