为啥说java spi破坏双亲委派模型( 四 )


为啥说java spi破坏双亲委派模型

java.sql.Driver 是最为典型的 SPI 接口,java.sql.DriverManager 通过扫包的方式拿到指定的实现类,完成 DriverManager的初始化。
等等,似乎有什么不对,根据双亲委派的可见性原则,启动类加载器 加载的 DriverManager 是不可能拿到 系统应用类加载器 加载的实现类 ,这似乎通过某种机制打破了双亲委派模型。

双亲委派模型并非强制模型SPI 是如何打破双亲委派模型的呢?
java.sql.DriverManager#loadInitialDrivers
ServiceLoader\u0026lt;Driver\u0026gt; loadedDrivers = ServiceLoader.load(Driver.class);Iterator\u0026lt;Driver\u0026gt; driversIterator = loadedDrivers.iterator();?/* Load these drivers, so that they can be instantiated. * It may be the case that the driver class may not be there * i.e. there may be a packaged driver with the service class * as implementation of java.sql.Driver but the actual class * may be missing. In that case a java.util.ServiceConfigurationError * will be thrown at runtime by the VM trying to locate * and load the service. * * Adding a try catch block to catch those runtime errors * if driver not available in classpath but it\u0026#39;s * packaged as service and that service is there in classpath. */try{ while(driversIterator.hasNext()) { driversIterator.next(); }} catch(Throwable t) {// Do nothing}return null;java.util.ServiceLoader#load(java.lang.Class\u0026lt;S\u0026gt;)
public static \u0026lt;S\u0026gt; ServiceLoader\u0026lt;S\u0026gt; load(Class\u0026lt;S\u0026gt; service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl);}通过从线程上下文(ThreadContext)获取 classloader ,借助这个classloader 可以拿到实现类的 Class。
(源码上讲,这里是通过 Class.forName 配合 classloader拿到的)
线程上下文 classloader并非具体的某个loader,一般情况下是 application classloader, 但也可以通过 java.lang.Thread#setContextClassLoader 这个方法指定 classloader。

综上,为什么说 Java SPI 的设计会违反双亲委派原则呢?
首先双亲委派原则本身并非 JVM 强制模型。
SPI 的调用方和接口定义方很可能都在 Java 的核心类库之中,而实现类交由开发者实现,然而实现类并不会被启动类加载器所加载,基于双亲委派的可见性原则,SPI 调用方无法拿到实现类。
SPI Serviceloader 通过线程上下文获取能够加载实现类的classloader,一般情况下是 application classloader,绕过了这层限制,逻辑上打破了双亲委派原则。

参考资料

Understanding Extension Class Loading
Class Loaders in Java | BaeldungChapter 5. Loading, Linking, and Initializing
https://javarevisited.blogspot.com/2012/12/how-classloader-works-in-java.html
个人公众号
ZeaTalk

■网友
使用Class.forName()不就是相当于在使用的时候把类名写死了么,SPI就是要解决这个问题,我提供一个接口,你需要自己的实现类直接打个JAR包发到classpath就可以了。
■网友
看ServiceLoader的源码就知道了:
private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class\u0026lt;?\u0026gt; c = null; try { //这个loader如果不指定是默认用的AppClassLoader 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 }


推荐阅读