JAVA(3)- 线程上下文类加载器
前言
线程上下文类加载器扩展:
概念
SPI:Service Provider Interface 、由JDK定义的一套标准接口,方便扩展各个厂商的不同实现
1、为什么需要线程上下文类加载器
- JDK核心库中提供了很多SPI(Service Provider Interface),常见的SPI包括JDBC、JCE、JNDI、JAXP、和JBI等、JDK只规定了这些接口之间的逻辑关系,但不提供具体的实现,具体的实现由第三方厂商提供,这样做的好处是JDBC提供了高度抽象,应用程序只需要面向接口编程。
- 问题:java.lang.sql中的所有接口都由JDK提供、加载这些类的是根类加载器,第三方提供的是系统应用类加载器,由于JVM的双亲委托机制,比如Connections、Statement、RowSet等都是由根类加载器加载、第三方的类不会被加载,那是如何解决这个问题的呢?
2、数据库驱动初始化源码分析
- rt.jar包 DriverManager 是BootstrapClassLoader加载
- 父类加载器委托子类类加载器去加载 破坏双亲委派模型
callerCL = Thread.currentThread().getContextClassLoader();
aClass = Class.forName(driver.getClass().getName(), true, classLoader);
// Worker method called by the public getConnection() methods.
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
// Walk through the loaded registeredDrivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
boolean result = false;
if(driver != null) {
Class<?> aClass = null;
try {
aClass = Class.forName(driver.getClass().getName(), true, classLoader);
} catch (Exception ex) {
result = false;
}
result = ( aClass == driver.getClass() ) ? true : false;
}
return result;
}
3、总结
- 在Thread中设置以下两个方法就是为了解决SPI的问题
Thread.currentThread().getContextClassLoader()
Thread.currentThread().setContextClassLoader();
- 由父委托变为子委托的方式,打破了双亲委托机制的模型,几乎所有涉及SPI加载的动作采用的都是这种方式。
- 现在开源社区会经常遇到标准的接口和第三方实现独立设计的情况,比如slf4j只是log的标准接口库,而slf4j-log4j则是其中的一个实现,在真实项目中,两者皆由同一个类加载器进行加载。