在路上

 找回密码
 立即注册
在路上 站点首页 学习 查看内容

java spi 深入研究以及 ClassLoader

2017-2-7 13:43| 发布者: zhangjf| 查看: 664| 评论: 0

摘要: SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。 具体 SPI 的介绍详细看下面的资料: Java SPI机制简介 Thread.currentThread().getContextClassLoader() 类加载器的简单介绍看如 ...

SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。

具体 SPI 的介绍详细看下面的资料:

Java SPI机制简介

Thread.currentThread().getContextClassLoader()

类加载器的简单介绍看如下资料:

Java 类加载器

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

为什么会有线程上下文类加载器

Thread context class loader 存在的目的主要是为了解决 parent delegation 机制下无法干净的解决的问题。

假如有下述委派链:

ClassLoader A -> System class loader -> Extension class loader -> Bootstrap class loader

那么委派链左边的 ClassLoader 就可以很自然的使用右边的 ClassLoader 所加载的类。

但如果情况要反过来,是右边的 Bootstrap class loader 所加载的代码需要反过来去找委派链靠左边的 ClassLoader A 去加载东西怎么办呢?没辙,parent delegation 是单向的,没办法反过来从右边找左边。

这种情况下就可以把某个位于委派链左边的 ClassLoader 设置为线程的 context class loader,这样就给机会让代码不受 parent delegation 的委派方向的限制而加载到类了。

例子

JDBC 接口,我在 classpath 下面引入了 jdbc 的 jar 包。

看如下代码:

  1. public class TestMain {
  2. public static void main(String[] args) throws Exception {
  3. Enumeration<Driver> dEnumeration = DriverManager.getDrivers();
  4. while (dEnumeration.hasMoreElements()) {
  5. Driver driver = (Driver) dEnumeration.nextElement();
  6. System.out.println(driver.getClass() + " : " + driver.getClass().getClassLoader());
  7. }
  8. System.out.println(Thread.currentThread().getContextClassLoader());
  9. System.out.println(DriverManager.class.getClassLoader());
  10. }
  11. }
复制代码

输出结果如下:
  1. class com.mysql.jdbc.Driver : sun.misc.Launcher$AppClassLoader@2e5f8245
  2. class com.mysql.fabric.jdbc.FabricMySQLDriver : sun.misc.Launcher$AppClassLoader@2e5f8245
  3. sun.misc.Launcher$AppClassLoader@2e5f8245
  4. null
复制代码

我们从结果可以看到,我们并没有注册 Driver 然后就可以获得到 Dirver,并且还可以看到他们的类加载器都是系统加载器。而 DriverManager 的类加载器是 null,也就是说他的加载器是 Bootstrap class loader。

那我们看下 DriverManager 源码:

  1. static {
  2. loadInitialDrivers();
  3. println("JDBC DriverManager initialized");
  4. }
  5. private static void loadInitialDrivers() {
  6. ... 省略
  7. AccessController.doPrivileged(new PrivilegedAction<Void>() {
  8. public Void run() {
  9. ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
  10. Iterator driversIterator = loadedDrivers.iterator();
  11. try{
  12. while(driversIterator.hasNext()) {
  13. driversIterator.next();
  14. }
  15. } catch(Throwable t) {
  16. // Do nothing
  17. }
  18. return null;
  19. }
  20. });
  21. ... 略
  22. }
复制代码

看主要的部分就是我们使用 SPI 的过程,先加载实现了 Driver 的实现类。

看下 ServiceLoader 是如何进行加载的。

  1. public static <S> ServiceLoader<S> load(Class<S> service) {
  2. ClassLoader cl = Thread.currentThread().getContextClassLoader();// 使用上下文类加载器,加载器链的逆向使用
  3. return ServiceLoader.load(service, cl);
  4. }
  5. public static <S> ServiceLoader<S> load(Class<S> service,
  6. ClassLoader loader)
  7. {
  8. return new ServiceLoader<>(service, loader);
  9. }
复制代码

我们创建了一个 ServiceLoader 实例:

下面是 ServiceLoader 的属性:

  1. private static final String PREFIX = "META-INF/services/";//SPI 默认读的路径
  2. // The class or interface representing the service being loaded
  3. private Class<S> service;//服务的 Class 对象
  4. // The class loader used to locate, load, and instantiate providers
  5. private ClassLoader loader;// 类加载器,传入的是线程上下文类加载器
  6. // Cached providers, in instantiation order
  7. private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 服务的实现者
  8. // The current lazy-lookup iterator
  9. private LazyIterator lookupIterator;
复制代码

下面是构造方法以及 reload 方法:
  1. public void reload() {
  2. providers.clear();//清空了 map
  3. lookupIterator = new LazyIterator(service, loader);// 创建 LazyIterator 迭代器
  4. }
  5. private ServiceLoader(Class<S> svc, ClassLoader cl) {
  6. service = svc;
  7. loader = cl;
  8. reload();// 当创建实例的时候会调用这个 reload 方法。
  9. }
复制代码

看下 LazyIterator 迭代器的实现,从名字就可以看出来是一个懒加载的迭代器。

  1. private class LazyIterator
  2. implements Iterator<S>
  3. {
  4. Class<S> service;
  5. ClassLoader loader;
  6. Enumeration<URL> configs = null;
  7. Iterator<String> pending = null;
  8. String nextName = null;
  9. private LazyIterator(Class<S> service, ClassLoader loader) {
  10. this.service = service;
  11. this.loader = loader;
  12. }
  13. public boolean hasNext() {
  14. if (nextName != null) {
  15. return true;
  16. }
  17. if (configs == null) {
  18. try {
  19. String fullName = PREFIX + service.getName();
  20. if (loader == null)
  21. configs = ClassLoader.getSystemResources(fullName);
  22. else
  23. configs = loader.getResources(fullName);
  24. } catch (IOException x) {
  25. fail(service, "Error locating configuration files", x);
  26. }
  27. }
  28. while ((pending == null) || !pending.hasNext()) {
  29. if (!configs.hasMoreElements()) {
  30. return false;
  31. }
  32. pending = parse(service, configs.nextElement());
  33. }
  34. nextName = pending.next();
  35. return true;
  36. }
  37. public S next() {
  38. if (!hasNext()) {
  39. throw new NoSuchElementException();
  40. }
  41. String cn = nextName;
  42. nextName = null;
  43. Class<?> c = null;
  44. try {
  45. c = Class.forName(cn, false, loader);
  46. } catch (ClassNotFoundException x) {
  47. fail(service,
  48. "Provider " + cn + " not found");
  49. }
  50. if (!service.isAssignableFrom(c)) {
  51. fail(service,
  52. "Provider " + cn + " not a subtype");
  53. }
  54. try {
  55. S p = service.cast(c.newInstance());
  56. providers.put(cn, p);
  57. return p;
  58. } catch (Throwable x) {
  59. fail(service,
  60. "Provider " + cn + " could not be instantiated",
  61. x);
  62. }
  63. throw new Error(); // This cannot happen
  64. }
  65. public void remove() {
  66. throw new UnsupportedOperationException();
  67. }
  68. }
复制代码

下面看一下 ServiceLoader 的 iterator 方法的实现:

  1. public Iterator<S> iterator() {
  2. return new Iterator<S>() {
  3. Iterator<Map.Entry<String,S>> knownProviders
  4. = providers.entrySet().iterator();
  5. public boolean hasNext() {
  6. if (knownProviders.hasNext())
  7. return true;
  8. return lookupIterator.hasNext();
  9. }
  10. public S next() {
  11. if (knownProviders.hasNext())
  12. return knownProviders.next().getValue();
  13. return lookupIterator.next();
  14. }
  15. public void remove() {
  16. throw new UnsupportedOperationException();
  17. }
  18. };
  19. }
复制代码

可以看出来对 LazyIterator 进行了一层包装,每次在迭代的时候会把发现的提供者加入到 ServiceLoader 内部的一个 map 当中。

回到 JDBC 的那个例子当中,为什么我们会自动进行注册呢,这个需要看一下 LazyIterator 的 next 方法执行了如下:

  1. c = Class.forName(cn, false, loader);
复制代码

同时我们在 DriverManager 当中也进行的迭代。最关键的就是 JDBC 规范了,Driver 的实现必须在初始化的时候进行注册。下面看 mysql 里面的实现就清楚了:

  1. static {
  2. try {
  3. java.sql.DriverManager.registerDriver(new Driver());
  4. } catch (SQLException E) {
  5. throw new RuntimeException("Can't register driver!");
  6. }
  7. }
复制代码

参考资料】 http://hllvm.group.iteye.com/group/topic/38709 http://www.ibm.com/developerworks/cn/java/j-lo-classloader/ http://www.ibm.com/developerworks/cn/java/j-dyn0429/

---EOF---

来自: http://renchx.com/java-spi-serviceloader/

最新评论

小黑屋|在路上 ( 蜀ICP备15035742号-1 

;

GMT+8, 2025-7-9 15:20

Copyright 2015-2025 djqfx

返回顶部