一.局限性
父加载器无法向下识别子加载器加载的资源(假定 A 作为 B 的 parent,A 加载的类 对 B 是可见的;然而 B 加载的类 对 A 却是不可见的)这是由 classloader 加载模型中的可见性(visibility)决定的。
二.实际体现
最典型不适用的场景便是 SPI 的使用。Java 在核心类库中定义了许多接口,并且还给出了针对这些接口的调用逻辑,然而并未给出实现。开发者要做的就是定制一个实现类,在 META-INF/services 中注册实现类信息,以供核心类库使用。java.sql.Driver 是最为典型的 SPI 接口,java.sql.DriverManager 通过扫包的方式拿到指定的实现类,完成 DriverManager的初始化。<br />根据双亲委派的可见性原则,**启动类加载器** 加载的 DriverManager 是不可能拿到 **系统应用类加载器** 加载的实现类 ,这似乎通过某种机制打破了双亲委派模型。<br />为解决上述问题,引入线程上下文类加载器,可以通过Thread的setContextClassLoader设置
SPI 是如何打破双亲委派模型的呢?
java.sql.DriverManager#loadInitialDrivers
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);Iterator<Driver> 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's* packaged as service and that service is there in classpath.*/try{while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {// Do nothing}return null;
public static <S> ServiceLoader<S> load(Class<S> 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,绕过了这层限制,逻辑上打破了双亲委派原则。
