SPI 机制

Java提供了很多SPI,允许第三方为这些接口提供实现,常见SPI实现有JDBC、JNDI等

根据类加载器的双亲委派模型,加载ServiceLoader的 BootstrapClassLoader不能能委派 AppClassLoader 加载SPI实现类

默认情况下,Java应用的线程上下文类加载器默认是AppClassLoader,这样ServiceLoader就可以加载SPI实现类了

  
// 获取当前调用线程的类加载器
public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
}

Tomcat与Spring的加载

Tomcat基本遵守JVM的委派模型,但做了调整

Tomcat的类加载体系图:

CommonClassLoader 能加载的类都可以被 CatalinaClassLoader 和 SharedClassLoader 使用,而 CatalinaClassLoader 和 SharedClassLoader 自己能加载的类则与对方相互隔离。WebAppClassLoader 可以使用 SharedClassLoader 加载到的类,但各个 WebAppClassLoader 实例之间相互隔离

Spring Boot 的 AppClassLoader

Spring Boot 嵌入式 Tomcat 运行方式,使整个应用在单一 JVM 进程中运行,使用AppClassLoader,而不是传统的 Web 应用程序类加载器 (WebappClassLoader)

没有传统分离的 Web 应用上下文的需要,简化类加载流程,避免一些类加载器冲突问题

使用 AppClassLoader 简化了类加载流程,也更适合 Spring Boot 的设计哲学

@SpringBootApplication
public class ClassLoaderDemoApplication implements CommandLineRunner {
 
    @Autowired
    private ServletContext servletContext;
 
    public static void main(String[] args) {
        SpringApplication.run(ClassLoaderDemoApplication.class, args);
    }
 
    @Override
    public void run(String... args) throws Exception {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        System.out.println("ClassLoader: " + classLoader);
        
        if (servletContext != null) {
            System.out.println("Servlet Context Class Loader: " + servletContext.getClassLoader());
        }
    }
}