Java中线程上下文类加载器超详细讲解使用
前言
在Java多线程中,经常会出现跨类加载器的情况,例如Web容器中的应用程序的类加载器和Java线程在使用的类加载器可以是不同的实例。而在Java中,不同的类加载器对于同一个类的加载得到的Class对象实例是不同的,这样就会导致在不同的类加载器中创建的对象实例无法相互转换,从而引发一系列问题。为此,Java中引入了线程上下文类加载器的概念。本文将会对这个概念进行详细讲解,并且提供两条实例。
线程上下文类加载器是什么?
线程上下文类加载器是Java提供的一种机制,在多线程的场景中可以让当前线程加载指定的类,从而解决了跨类加载器的问题。每个线程都可以通过设置上下文类加载器,让该线程在加载类时优先使用这个类加载器。当该线程找不到某个类时,将会委托其上下文类加载器去加载这个类。
线程上下文类加载器的使用方法
Java提供了Thread类中的getContextClassLoader()和setContextClassLoader(ClassLoader cl)方法,这两个方法可以操作线程的上下文类加载器。下面分别对它们进行说明。
getContextClassLoader()方法
getContextClassLoader()方法用来获取线程的上下文类加载器,方法签名如下:
public ClassLoader getContextClassLoader()
调用该方法可以得到该线程当前正在使用的上下文类加载器,如果未设置该线程的上下文类加载器,则返回父线程的上下文类加载器(默认情况下,所有线程的上下文类加载器都是其父线程的上下文类加载器)。
setContextClassLoader(ClassLoader cl)方法
setContextClassLoader(ClassLoader cl)方法用于设置线程的上下文类加载器,方法签名如下:
public void setContextClassLoader(ClassLoader cl)
该方法接受一个类加载器作为参数,用于设置该线程的上下文类加载器为传入的类加载器。如果不设置该线程的上下文类加载器,那么设置即使不设置,默认情况下,该线程的上下文类加载器会继承父线程的上下文类加载器。
线程上下文类加载器的使用示例
Java中线程上下文类加载器的使用场景很多,这里我们提供两个示例来说明。
示例1
在Java中很多第三方框架都使用了SPI (Service Provider Interface)机制,在SPI机制中,服务提供者会提供一个标准的接口,并会将该接口实现类的全类名记录在一个配置文件中,类似于 Java中的META-INF/service目录中的文件。这样当应用程序运行时,使用该接口的程序可以通过在配置文件中查找实现类的全类名,并通过反射机制创建该实现类的实例。
在SPI机制中,如果服务提供商和服务使用者不在同一个类加载器中,那么在进行实例初始化时就会报类转换异常。此时,可以通过设置线程上下文类加载器来解决该问题,代码示例如下:
public class MyServiceLoader<T> {
private static final String PREFIX = "META-INF/services/";
private Class<T> service;
public MyServiceLoader(Class<T> service) {
this.service = service;
}
public T load() throws Exception {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
String file = PREFIX + service.getName();
Enumeration<URL> urls = contextClassLoader.getResources(file);
while (urls != null && urls.hasMoreElements()) {
URL url = urls.nextElement();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"))) {
String line;
while ((line = reader.readLine()) != null) {
Class<?> clazz = contextClassLoader.loadClass(line);
if (clazz != null && service.isAssignableFrom(clazz)) {
return (T) clazz.newInstance();
}
}
}
}
return null;
}
}
示例2
当应用程序使用了一些比较底层的Java库时,就会可能出现跨类加载器的情况。例如,有一个app.jar包中的类需要使用Java标准库中的类。但是如果使用了不同的类加载器,就可能会造成类转换异常,因为这些类都不在同一个类加载器中。此时,可以通过在Thread.currentThread().setContextClassLoader(ClassLoader)中设置上下文类加载器为根类加载器来解决该问题,代码示例如下:
public class App {
public static void main(String[] args) throws Exception {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
//使用根类加载器加载必要的类
Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass("java.lang.String");
System.out.println(clazz.getClassLoader());
//...
//使用完还原线程上下文类加载器
Thread.currentThread().setContextClassLoader(contextClassLoader);
}
}
结论
线程上下文类加载器是Java应用程序中处理跨类加载器问题的有力工具,理解和掌握这个机制对于进行Java开发工作至关重要。当应用程序中存在跨类加载器的情况时,可以通过设置线程上下文类加载器来解决这些问题。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java中线程上下文类加载器超详细讲解使用 - Python技术站