文章目录
1. 简介
线程上下文类加载器
(context class loader)是从 JDK 1.2
开始引入的。类 java.lang.Thread
中的方法 getContextClassLoader()
和 setContextClassLoader(ClassLoader cl)
用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)
方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器(AppClassLoader,有的翻译为应用程序类加载器)。在线程中运行的代码可以通过此类加载器来加载类和资源。
可以参考加载器之sun.misc.Launcher类获取初始线程的上下文类加载器是系统类加载器的知识。sun.misc.Launcher类是java的入口,在其构造函数中,设置了线程上下文类加载器。
2. 用法
在java.lang.Thread
类中有两个方法,分别提供设置和获取上下文类加载器的方法
public ClassLoader getContextClassLoader() //获取到当前线程的上下文类加载器
public void setContextClassLoader(ClassLoader cl) //设置当前线程的上下文类加载器
如果当前线程没有设置上下文类加载器,那么它就会和父线程保持同样的类加载器,也就是说Main线程使用什么样的类加载器其子类线程就使用什么样的类加载器。如下:
public class Test {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getContextClassLoader());
}
}
JDK1.8 执行结果:
sun.misc.Launcher$AppClassLoader@19821f
3. 上下文类加载器的作用
为什么会有线程上下文类加载器的出现呢?这个就是因为我们类加载的双亲委托机制的缺陷
,因为JDK核心类库由BootStrapClassLoader
类加载器中进行加载,在JDK核心类库中提供了很多SPI
(Service Provider Interface),其中比较常用的就是关于JDBC的SPI接口。JDK也只是规定的这个接口的之间的实现逻辑关系,并没有提供具体的怎么样的实现。这些具体实现都是我们通过第三方的厂商来提供的,例如使用MySQL
的时候我们要引入MySQL的驱动,使用Oracle的时候要使用Oracle的驱动等等。如图所示,Java使用JDBC这个SPI完全的实现了应用和第三方数据库驱动的实现的接入。在使用的时候只需要更换对应的jar包和数据库驱动,其他的一概不变。
常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等,接口均定义在rt.jar中
这样做的好处是JDBC提供了对于数据库操作的高度封装,应用程序只需要实现对应的接口即可,而对于我们持久层的框架来说就是对这些接口的再次的封装,使得我们开发更加的简便。从上图也可以看出我们在实际使用的时候各大数据库提供者都实现了具体的底层驱动,使用者并不需要关心这些。
就拿MySQL驱动举例子来说,在使用的时候我们是通过Class.forName()
这个方法来将数据库的驱动引入到其中。但是是谁去加载其中的Class文件呢?下面我们就来深入的分析一下关于MySQL驱动的的初始化以及源码加载过程。
配置maven依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<verion>5.1.38</verion>
</dependency>
常规调用mysql连接的例子:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCTest {
private static Connection connection;
private static PreparedStatement preparedStatement;
private static ResultSet resultSet;
public static void main(String[] args) throws SQLException {
try {
String url = "jdbc:mysql://localhost:3306/testdb";
Class.forName("com.mysql.jdbc.Driver"); // 加载驱动
connection = DriverManager.getConnection(url, "name", "password");
preparedStatement.execute("SELECT * FROM student");
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
System.out.println(resultSet.getInt(1) + resultSet.getInt(2));
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
从上述代码,我们有几个疑问:
-
1 “com.mysql.jdbc.Driver” 和"jdbc:mysql" 有什么关系,为什么要写两遍
-
2 Class.forName 注册驱动后,没有返回值,是怎么回事
-
3 Class.forName 为什么 此行在jdk6版本中 又不需要了呢
-
4 DriverManager.getConnection怎么使用的驱动的呢
3.1 class.forName()
一般使用jdbc 连接数据库,我们使用class.forName()加载驱动,下面我们看下原理:
3.1.1 com.mysql.jdbc.Driver类的静态语句块触发注册驱动
通过深入理解Java 反射中 Class.forName 和 ClassLoader 的区别可以知道 Class.forName
会在加载"com.mysql.jdbc.Driver
"驱动类的时候,会执行静态语句块,那么我们看下静态语句块里面有什么内容:
package com.mysql.jdbc; //位于mysql-connector-java-5.1.38.jar中
import java.sql.Driver; //spi接口,位于rt.jar
import java.sql.DriverManager;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements Driver {
//实现类rt.jar中的Driver接口
static {
//静态语句块
try {
DriverManager.registerDriver(new Driver()); //向DriverManager中注册当前驱动
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
}
在com.mysql.jdbc.Driver
的静态代码块中调用DriverManager.getConnection(),将Driver注册给了DriverManager
3.1.2 DriverManager.getConnection获取驱动
驱动注册了,DriverManager中 驱动怎么被使用呢
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
....
// 所有已经注册驱动都保存在registeredDrivers,这是个CopyOnWriteArrayList
for(DriverInfo aDriver : registeredDrivers) {
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
//jdbc:oracle:thin:@localhost:1521:orcl 最终是由驱动实现类使用
Connection con = aDriver.driver.connect(url, info);
遍历 registeredDrivers,找到符合权限条件的驱动,这里暂不讨论权限的问题。
3.2 SPI
我们前面提出的疑问,“ jdk6为什么不需要 执行了Class.forName这行代码了呢”?
原因就是SPI,什么是SPI,可以参考SPI介绍及实例分析,这篇文章里通过一个例子描述了SPI的加载原理。
从Java1.6开始自带的jdbc4.0版本已支持SPI服务加载机制,只要mysql的jar包在类路径中,就可以注册mysql驱动。
虽然我们我们知道了SPI加载的原理,但那到底是在哪一步自动注册了mysql driver的呢?我们又没有在main方法中手动调用:
ServiceLoader.load(Subscribe.class); //摘自《SPI介绍及实例分析》
其实答案就在DriverManager.getConnection()
中。我们都是知道调用类的静态方法会初始化该类,进而执行其静态代码块,DriverManager的静态代码块里面有触发SPI加载的入口:
public class DriverManager {
static {
//静态语句块
loadInitialDrivers(); //调用初始化驱动方法
println("JDBC DriverManager initialized");
}
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); //关键代码,是不是和main方法中调用的方式类似?
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next(); //真正加载,因为之前是懒加载
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
ServiceLoader.load(Driver.class);
是关键代码,是不是和《SPI介绍及实例分析》main方法中调用ServiceLoader.load(Subscribe.class)
的方式类似?是的,很相似,并在load方法中,用到了线程上下文线程类加载器。详情在《SPI介绍及实例分析》中搜索“上下文线程类”,即可找到具体的代码。
深入理解Java类加载器(2):线程上下文类加载器
介绍了线程上下文类加载器的起源
真正理解线程上下文类加载器(多案例分析) DriverManager初始化 & tomcat加载 spring