Java SPI发现机制

违背双亲委派模型的例子

Posted by     "Eric" on Friday, February 17, 2023

阅读本篇文章前默认已经掌握了Java的双亲委派模型。

最近在看Tomcat的自定义类加载器,在Tomat中一个JVM环境可能运行多个web application,为了对web application进行环境隔离,比如每个web application可以加载同名Servlet,为每一个Context定义了自己的类加载器,打破了Java类加载的双亲委派模型。

另外一个违背了Java类加载双亲委派模型的例子是Java的SPI机制。所谓Java SPI指的就是框架的开发者提供接口,然后服务的提供者提供接口的实现,调用者调用接口,如下图所示

image-20230217115417844

Java SPI一个典型的例子就是JDBC接口java.sql.Driver的实现。Java提供了一个接口,数据库的服务商提供其实现,比如MySQL,PostgreSQL等等。

其实现机制是在项目工程下创建META-INF/services目录,然后创建java.sql.Driver文件,在该文件内提供实现了java.sql.Driver接口的文件的全类名,比如在mysql-connector-java-8.0.28.jar中有META-INF/services/java.sql.Driver文件,该文件的内容为

com.mysql.cj.jdbc.Driver

根据该路径,我们发现该类的确实现了java.sql.Driver接口

package com.mysql.cj.jdbc;

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

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

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

在Java中,我们通常通过以下方式得到JDBC连接

String url = "jdbc:xxxx://xxxx:xxxx/xxxx";
Connection conn = DriverManager.getConnection(url,username,password);
.....

我们追踪到DriverManager这个类中,该类有一个静态代码块,在调用该类的静态方法的时候会被调用

static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

根据方法名可知,jdbc的driver在该静态代码块中被加载。**但是这里会有一个疑问,DriverManager是JAVA_HOME/rt.jar包中的类,因此该类的类加载器应该是最上层的Bootstrap ClassLoader。然而java.sql.Driver的各个实现类是无法写到rt.jar路径下,那又如何在DriverManager类中去加载服务商提供的各个实现呢?**这里就打破了双亲委派模型,用到了上下文类加载器。在该类的loadInitialDrivers方法中有这样一段代码,如下所示:

private static void loadInitialDrivers() {
  	...

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

其中ServiceLoader是SPI类加载器,其load方法中会拿到线程上下文类加载器,该类加载器就是AppClassLoader

public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

继续追踪代码,在ServiceLoader#load(Class<S> service, ClassLoader loader)中初始化了成员属性

// The class or interface representing the service being loaded
this.service = Objects.requireNonNull(svc, "Service interface cannot be null");
// The class loader used to locate, load, and instantiate providers
this.loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; 
// The access control context taken when the ServiceLoader is created
this.acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
// Cached providers, in instantiation order
this.providers.clear();
// The current lazy-lookup iterator
this.lookupIterator = new LazyIterator(service, loader);

回到DriverManager#loadInitialDrivers方法中,其调用了ServiceLoader#iterator方法返回了一个迭代器,在该迭代器中,会先在providers中看是否缓存了类,如果没有则调用lookupIterator.hasNextlookupIterator.next(正是因为这个原因所以称为懒加载迭代器吧)

/**
* 获取已加载服务迭代器,默认为空,通过hasNext方法加载
*/
public Iterator<S> iterator() {
    return new Iterator<S>() {
        //获取当前已加载服务迭代器
        Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
        // 重写Iterator.hasNext方法
        // 判断已加载迭代器中是否存在下一个,存在则返回true
        // 否则返回待加载迭代器中是否存在下一个
        public boolean hasNext() {
            if (knownProviders.hasNext())
                return true;
            return lookupIterator.hasNext();
        }
        // 重写Iterator.next方法
        // 判断已加载迭代器中是否存在下一个,存在则返回这个实现类
        // 否则返回待加载迭代器中的下一个,为空抛出异常
        public S next() {
            if (knownProviders.hasNext())
               return knownProviders.next().getValue();
            return lookupIterator.next();
        }
        //重写方法,不允许溢出
        public void remove() {
            throw new UnsupportedOperationException();
        }
    };
}
private class LazyIterator implements Iterator<S>{
		// 提供服务类 即 要加载的接口
    Class<S> service;
  	// 类加载器
    ClassLoader loader;
  	// META-INF/services/java.sql.Driver文件所在的位置URL
    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 {
                // 服务接口类地址 
                // META-INF/services/java.sql.Driver
                String fullName = this.PREFIX + this.service.getName();
              	// 拿到META-INF/services/java.sql.Driver文件的所有地址URL
                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;
            }
          	// 解析java.sql.Driver文件,拿到里面的全类名
            pending = parse(service, configs.nextElement());
        }
      	// 将全类名赋值给nextName
        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);
        }
    }

}

经过以上讲解,应该清晰的知道了为什么Java SPI需要打破双亲委派模型,以及Java SPI如何使用线程上下文类加载器去加载类。

下面是自己写的一个例子,spi-demo