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:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问
-
CatalinaClassLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见
-
SharedClassLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见
-
WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见
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());
}
}
}