# 4.5 线程上下文类加载器
在前面的几节中,重点分析了双亲委派模型的实现原理,可以得出一个基本的结论: 子类加载器可以使用父类加载器已经加载的类,而父类加载器无法使用子类加载器已经加载的。 这就导致了双亲委派模型并不能解决所有的类加载器问题。
例如,Java中提供了一些接口(Service Provider Interface,SPI)), 如JDBC、JNDI和JAXP等,这些接口类由BootstrapClassLoader加载, 但是这些接口的实现却是由第三方提供,一般是由AppClassLoader来加载的。 而BootstrapClassLoader无法加载到核心接口的实现类的,因为它只加载Java的核心库。 它也不能代理给AppClassLoader,因为它是最顶层的类加载器。也就是说,双亲委派模型并不能解决这个问题。
Java 为了解决这个问题,引入了线程上下文类加载器(ContextClassLoader)加载。
# 5.1 SPI原理
Java内置的SPI通过java.util.ServiceLoader类解析classPath和jar包的META-INF/services/目录 下的以接口全限定名命名的文件,并加载该文件中指定的接口实现类,以此完成调用。
public interface VedioSPI {
void call();
}
2
3
public class Mp3Vedio implements VedioSPI {
@Override
public void call() {
System.out.println("this is mp3 call");
}
}
2
3
4
5
6
7
public class Mp4Vedio implements VedioSPI {
@Override
public void call() {
System.out.println("this is mp4 call");
}
}
2
3
4
5
6
在项目的source目录下新建META-INF/services/目录下, 创建com.skywares.fw.juc.spi.VedioSPI文件。
public class VedioSPITest {
public static void main(String[] args) {
ServiceLoader<VedioSPI> serviceLoader = ServiceLoader.load(VedioSPI.class);
serviceLoader.forEach(t->{
t.call();
});
}
}
2
3
4
5
6
7
8
Java实现spi是通过ServiceLoader来查找服务提供的工具类。
# 源码分析
上述只是通过简单的示例来实现下java的内置的SPI功能。 其实现原理是ServiceLoader是Java内置的用于查找服务提供接口的工具类, 通过调用load()方法实现对服务提供接口的查找,最后遍历来逐个访问服务提供接口的实现类。
从源码可以发现:
ServiceLoader类本身实现了Iterable接口并实现了其中的iterator方法, iterator方法的实现中调用了LazyIterator这个内部类中的方法, 解析完服务提供接口文件后最终结果放在了Iterator中返回,并不支持服务提供接口实现类的直接访问。 所有服务提供接口的对应文件都是放置在META-INF/services/目录下,final类型决定了PREFIX目录不可变更。
虽然java提供的SPI机制的思想非常好,但是也存在相应的弊端。具体如下:
Java内置的方法方式只能通过遍历来获取 服务提供接口必须放到META-INF/services/目录下。 针对java的spi存在的问题,Spring的SPI机制沿用的SPI的思想,但对其进行扩展和优化