# 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();
}
1
2
3
public class Mp3Vedio implements VedioSPI {
    @Override
    public void call() {
        System.out.println("this is mp3 call");
    }
 
}
1
2
3
4
5
6
7
public class Mp4Vedio implements VedioSPI {
    @Override
    public void call() {
       System.out.println("this is mp4 call");
    }
}
1
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();
        });
    }
}
1
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的思想,但对其进行扩展和优化