SPI是什么
Java提供了很多SPI,允许第三方为这些接口提供实现,最常见的SPI实现有JDBC、JNDI等等,根据类加载器的双亲委派模型,加载ServiceLoader的 BootstrapClassLoader 是不能加载SPI的实现类的,因为SPI的实现类是由 AppClassLoader 加载的,而 BootstrapClassLoader 是不能委派 AppClassLoader 来加载类的,那该怎么办呢?
SPI约定为:当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
SPI就是
在resources目录下,新建META-INF/services
目录,然后新建文件(文件名=接口包名.接口名
)。内容就是实现类的包名.类名
SPI的接口是Java核心库的一部分,按照双亲委派模式和类加载器搜索路径而言:它是由启动类加载器来加载的;但是SPI的实现类在我们应用引入之后在应用Classpath下,BootstrapClassLoader不认识它加载不了,只能由系统类加载器来加载的。原因在于启动类加载器是无法找到 SPI 的实现类的(因为它只加载 Java 的核心库),按照双亲委派模型,启动类加载器又无法委派系统类加载器去加载类。也就是说,类加载器的双亲委派模式无法解决这个问题
这时候线程上下文类加载器排上了用场。线程上下文类加载器破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器。
线程上下文类加载器(TCCL)
可使用ServiceLoader的load方法获取到TCCL
private static final String PREFIX = "META-INF/services/";
public static <S> ServiceLoader<S> load(Class<S> service) {
// 获取当前调用线程的类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
Java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的线程上下文类加载器初始是AppClassLoader,在线程中运行的代码可以通过此类加载器来加载类和资源。
ServiceLoader<Lxw> loader = ServiceLoader.load(Lxw.class);
Iterator<Lxw> iterator = loader.iterator();
while (iterator.hasNext()) {
Lxw l = iterator.next();
l.say();
}
应用程序启动会走这个获取到系统类加载器,是应用程序类加载器,然后执行第一行进入load放方法
获取到线程上下文类加载器
,然后就是load了,new了一个serviceLoader
可以看到new了一个类出来
总结
直白一点说就是,我(JDK)提供了一种帮你(第三方实现者)加载服务(如数据库驱动、日志库)的便捷方式,只要你遵循约定(把类名写在/META-INF里),那当我启动时我会去扫描所有jar包里符合约定的类名,再调用forName加载,但我的ClassLoader是没法加载的,那就把它加载到当前执行线程的TCCL里,后续你想怎么操作(驱动实现类的static代码块)就是你的事了。
线程上下文类加载器(它并不是一个真正的类加载器,而是通过当前线程拿到我们想要的类加载器->应用运行时它被放在了线程中,所以不管当前程序处于何处BootstrapClassLoader或ExtClassLoader等,在任何需要的时候都可以拿出去使用)。
线程上下文类加载器打破了双亲委派机制,实现逆向调用类加载器来加载当前线程中类加载器加载不到的类当高层提供了统一接口让低层去实现,同时又要是在高层加载(或实例化)低层的类时,比如上面spi的调用者ServiceLoader所在的BootstrapClassloader无法加载的时候,必须通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类。
类/接口是有命名空间之分的,不同的类加载器是不同的命名空间。一个类是由A类加载器加载的,那么这个类的依赖类也会由这个A类加载器加载,但是如果所依赖的类不在A类加载器加载的范围内,那么就会找不到这个类。可以使用线程上下文类加载器进行加载使用。这种操作就是破坏了双亲委派模式