线程上下文类加载器的引入。
最好最常见的引入:对应得数据库连接包中
Class.forName("org.postgresql.Driver").newInstance();
String url ="jdbc:postgresql://localhost/myDB"
//myDB为数据库名
String user="myuser";
String password="mypassword";
Connection conn= DriverManager.getConnection(url,user,password);
通过上面可知,这些连接方式,肯定是最初已经加载了的,显然是bootstrap加载器已经将相关类加载。因此才能初始时我们可这样直接写(反问:什么都有依据,那么你凭什么就可以直接使用?原因就在早已经加载了Connection类,这些具体的定制都是由厂商制定的,我们只是将他放在了对应的位置,加载使用)
因此引入ContextClassLoader来破坏双亲委托机制,来实现上面的SPI加载。
package com.auto.demo;
/**
* 当前类加载器(current classloader):每个都会尝试用自己的类加载器去加载其他类
* 这就是下面是null的原因;
* ContextClassLoader(线程上下文加载器):Thread类中的getContextClassLoader与setContextClassLoader
* 用于获取和设置上下文加载器。
* 没有setContextClassLoader进行设置,默认线程将继承其父类的线程的上下文加载器。
* java应运行时初始线程的上下文类加载器是系统类加载器。
* 线程上下文加载器的重要性:
* ① SPI(Service Provide Interface)
* ② 父ClassLoader可以使用当前线程Thread.currentThread.getContextoader()所指定的classloader加载的类。
* 这就改变了父ClassLoader不能使用子ClassLoader或是其他没有直接父子关系的ClassLoader加载的类的情况,
* 即是改变了双亲委托机制。
* ③ 在双亲委托机制模型下,来加载器是由上至下的,即下层的类加载会委托上层进行加载。但是对于SPI来说,有些接口是java核心
* 库所提供的,而在java核心库是由启动类加载器加载的,而这些接口的实现却来自于不同的jar包(各大厂商提供),java的启动类加
* 载器是不会加载其他来源的jar包的,这样传统的双亲委托模型就无法满足SPI的要求,而通过给当前线程设置上下文类加载器
* 来实现对于接口实现类的加载。(框架开发极其常用)
* @author zhouyi
*
*/
public class MyContextTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getContextClassLoader()); // 系统类加载器
System.out.println(Thread.class.getClassLoader()); // 是由启动类加载加载,因此是null
}
}
运行结果:
public class MyContextTest implements Runnable {
private Thread thread;
public MyContextTest() {
thread = new Thread(this); // 因为是Runnable所以必须放入线程中
thread.start();
}
@Override
public void run() {
ClassLoader clLoader = this.thread.getContextClassLoader();
this.thread.setContextClassLoader(clLoader);
System.out.println("class: " + clLoader.getClass());
System.out.println("parent: " + clLoader.getParent().getClass());
}
public static void main(String[] args) {
new MyContextTest();
}
}
运行结果:
ServiceLoader
是服务提供者加载的集合,比如加载数据库连接,建议查看源码。
package com.auto.demo;
import java.sql.Driver;
import java.util.Iterator;
import java.util.ServiceLoader;
/**
*线程上下文类加载器的一般使用模式(获取 - 使用 - 还原)
*获取:ClassLoader clLoader = this.thread.getContextClassLoader();
*使用:
*try {
* Thread.currentThread().setContextClassLoader(targetClassLoader);
* myMethod(); // 我们自己要做操作的方法
*} finally {
* Thread.currentThread().getContextClassLoader(clLoader); // 还原
*}
*myMethod():调用了Thread.currentThread().getContextClassLoader(targetClassLoader);获取当前线程的上下文类加载器做某些事。
*
*当高层提供了统一的接口让低层实现,同时又要在高层加载(或实例化)低层的类时,
*就必须通过县城上下文类加载器来帮助高层的ClassLoader找到并加载。
* @author zhouyi
*
*/
public class MyContextTest {
public static void main(String[] args) {
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class); // 拿到数据库连接驱动
Iterator<Driver> it = loader.iterator();
while (it.hasNext()) {
Driver driver = it.next();
System.out.println("driver: " + driver + ",loader: " + driver.getClass().getClassLoader());
}
System.out.println("当前线程上下文类加载器: " + Thread.currentThread().getContextClassLoader());
System.out.println("service的类加载器: " + ServiceLoader.class.getClassLoader());
}
}
运行结果:
修改它的加载方式:(改为上层)
public class MyContextTest {
public static void main(String[] args) {
Thread.currentThread().setContextClassLoader(MyContextTest.class.getClassLoader().getParent());
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class); // 拿到数据库连接驱动
Iterator<Driver> it = loader.iterator();
while (it.hasNext()) {
Driver driver = it.next();
System.out.println("driver: " + driver + ",loader: " + driver.getClass().getClassLoader());
}
System.out.println("当前线程上下文类加载器: " + Thread.currentThread().getContextClassLoader());
System.out.println("service的类加载器: " + ServiceLoader.class.getClassLoader());
}
}
运行结果:
关于线程上下文类加载器,扩展到Servlet、tomcat和框架搭载相关的知识,有时间一定要去研究研究
接下来是java字节码
字节码文件结构解析