线程上下文类加载器(ThreadContextClassLoader,TCCL)
Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI接口中的代码经常需要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由启动类加载器(Bootstrap Classloader)来加载的;SPI的实现类是由系统类加载器(System ClassLoader)来加载的。引导类加载器是无法找到 SPI 的实现类的,因为依照双亲委派模型,BootstrapClassloader无法委派AppClassLoader来加载类。而线程上下文类加载器破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器。
JDBC案例分析
平时使用mysql获取数据库连接的:
// 加载Class到AppClassLoader(系统类加载器),然后注册驱动类
// Class.forName("com.mysql.jdbc.Driver").newInstance();
String url = "jdbc:mysql://localhost:3306/testdb";
// 通过java库获取数据库连接
Connection conn = java.sql.DriverManager.getConnection(url, "name", "password");
以上就是mysql注册驱动及获取connection的过程,各位可以发现经常写的Class.forName被注释掉了,但依然可以正常运行,这是为什么呢?这是因为从Java1.6开始自带的jdbc4.0版本已支持SPI服务加载机制,只要mysql的jar包在类路径中,就可以注册mysql驱动。
那到底是在哪一步自动注册了mysql driver的呢?重点就在DriverManager.getConnection()中。我们都是知道调用类的静态方法会初始化该类,进而执行其静态代码块,DriverManager的静态代码块就是:
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
初始化方法loadInitialDrivers()的代码如下:
private static void loadInitialDrivers() {
String drivers;
try {
// 先读取系统属性
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// 通过SPI加载驱动类
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {

本文详细介绍了Java的SPI(Service Provider Interface)机制,如何通过线程上下文类加载器(ThreadContextClassLoader,TCCL)加载SPI实现类。以JDBC为例,分析了DriverManager如何利用TCCL加载数据库驱动,并探讨了Tomcat的类加载器结构,以及Spring如何利用TCCL加载Web应用程序中的bean。
最低0.47元/天 解锁文章
7520

被折叠的 条评论
为什么被折叠?



