我们紧接着上一个例子的代码进行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class MyTest26 { public static void main(String[] args) { ServiceLoader<Driver> serviceLoader = ServiceLoader.load(Driver.class); Iterator<Driver> iterator = serviceLoader.iterator();
while(iterator.hasNext()){ Driver driver = iterator.next(); System.out.println("driver: "+driver.getClass() + "loader: "+ driver.getClass().getClassLoader() ); }
System.out.println("当前线程上下文类加载器: " + Thread.currentThread().getContextClassLoader());
System.out.println("ServiceLoader的类加载器: "+ServiceLoader.class.getClassLoader());
} }
|
首先MyTest26的类加载器是系统类加载器,
程序运行到【**ServiceLoader serviceLoader = ServiceLoader.load(Driver.class);**】的时候回去加载ServiceLoader,系统类加载器会根据双亲委托模型往上委派,直到最后ServiceLoader被启动类加载器加载,那么我们看一下ServiceLoader.load()方法的实现:
1 2 3 4
| public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
|
既然ServiceLoader是由启动类加载器加载,那么ServiceLoader里边的类都会用启动类加载器去加载,但是呢我们的mysql驱动不在启动类加载器加载的目录下边,我们的mysql驱动位于classpath下边,无法用启动类加载器加载,这个时候,我们可以看到load方法使用了线程上下文加载器,线程上下文加载器默认是系统类加载器上一节已经介绍过,所以load方法得到的ClassLoader cl是AppClassLoader,然后将cl传递给ServiceLoader.load(service, cl)方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| //加载器的缓存 // Cached providers, in instantiation order private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader) { //调用ServiceLoader构造器 return new ServiceLoader<>(service, loader); } //ServiceLoader构造器 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,刷新缓存 reload(); }
//刷新缓存 public void reload() { //清空缓存 providers.clear(); //LazyIterator是ServiceLoader的私有内部类 lookupIterator = new LazyIterator(service, loader); }
//LazyIterator 部分代码 // Private inner class implementing fully-lazy provider lookup //用来实现懒加载方式 的提供者的查找的私有内部类 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() { .... } private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<?> c = null; try { //cn相当于java.sql.Drive文件下的某一行的全限定名,false表示不初始化,loader加载器是我们传递进来的线程上下文类加载器 //(即,系统类加载器或者应用类加载器) //Class.forName()方法见之前的介绍:https://blog.csdn.net/wzq6578702/article/details/79950220 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() { ... } public S next() { ... } }
|
到此为止我们看到的程序的打印结果
1 2 3 4
| driver: class com.mysql.jdbc.Driverloader: sun.misc.Launcher$AppClassLoader@18b4aac2 driver: class com.mysql.fabric.jdbc.FabricMySQLDriverloader: sun.misc.Launcher$AppClassLoader@18b4aac2 当前线程上下文类加载器: sun.misc.Launcher$AppClassLoader@18b4aac2 ServiceLoader的类加载器: null
|
前2行driver就是java.sql.Drive文件里边的2行,而上下文类加载器是默认的系统类加载器,ServiceLoader属于rt.jar是由启动类加载器加载。
接下来改一下程序成如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class MyTest26 { public static void main(String[] args) { //添加这行代码 Thread.currentThread().setContextClassLoader(MyTest26.class.getClassLoader().getParent()); ServiceLoader<Driver> serviceLoader = ServiceLoader.load(Driver.class); Iterator<Driver> iterator = serviceLoader.iterator();
while(iterator.hasNext()){ Driver driver = iterator.next(); System.out.println("driver: "+driver.getClass() + "loader: "+ driver.getClass().getClassLoader() ); }
System.out.println("当前线程上下文类加载器: " + Thread.currentThread().getContextClassLoader());
System.out.println("ServiceLoader的类加载器: "+ServiceLoader.class.getClassLoader());
} }
|
打印结果:
1 2
| 当前线程上下文类加载器: sun.misc.Launcher$ExtClassLoader@14ae5a5 ServiceLoader的类加载器: null
|
可以看到循环没有去执行,上下文类加载器是扩展类加载器没啥问题,因为系统类加载器的上级是扩展类加载器,但是为什么循环是空的呢?原因就是扩展类加载器无法加载classpath下边的类,mysql的jar包是放在classpath下边的。
其实加上-XX:+TraceClassLoading启动参数,我们可以显式的看到驱动被加载。