我很难思考与Java 加载有关的东西,而不是与类加载器有关的东西。 在使用应用程序服务器或OSGi的情况下尤其如此,在这些应用程序服务器或OSGi中,经常使用多个类加载器,并且透明地使用类加载器的能力降低了。 我同意OSGI Alliance Blog文章中关于类加载器的了解 ,“在模块化环境中,类加载器代码会造成严重破坏。”

Neil Bartlett在博客文章The Dreaded Thread Context Class Loader上发表了文章,他在文章中描述了为什么引入了线程上下文类加载器以及为什么它的使用对OSGi不友好。 Bartlett指出,在极少数情况下,“一个库只咨询TCCL”,但在极少数情况下,“我们有些固执”,并且“在调用该库之前,必须从我们自己的代码中显式设置TCCL。”

Alex Miller还写了有关线程上下文类加载器 (TCCL)的文章,并指出“ Java框架没有遵循一致的类加载模式”,并且“许多常见且重要的框架的确使用了线程上下文类加载器(JMX,JAXP, JNDI ,等等)。” 他强调了这一点,“如果您使用的是J2EE应用服务器,那么几乎可以肯定,您依赖于使用线程上下文类加载器的代码。” 在那篇文章中 ,Miller提供了一种基于动态代理的解决方案,以在需要“设置线程上下文类加载器”然后“记住原始上下文类加载器并重新设置”的情况下提供帮助。

Knopflerfish Framework (一种OSGi实现)在其文档的“编程”部分中描述了如何使用线程上下文类加载器。 以下引用摘录自Knopflerfish 5.2的“编程”文档的“设置上下文类加载器”部分:


像大多数JNDI查找服务一样,许多外部库都需要正确设置
线程上下文类加载器 如果未设置,则即使包含了所有必需的库,也可能引发ClassNotFoundException或类似事件。 要解决此问题,只需在激活器中生成一个新线程并从该线程中进行工作即可。 … 它是
建议在启动线程上持久设置上下文类加载器,因为该线程对于您的捆绑包而言可能不是唯一的。 效果可能因OSGi供应商而异。 如果您不产生新线程,则您
返回之前, 必须重置上下文类加载器。

Knopflerish提供了一个简单的类org.knopflerfish.util.ClassLoaderUtil ,该类支持切换到提供的类加载器(在OSGi应用程序中可能经常是线程上下文类加载器),并通过finally子句确保重置了原始上下文类加载器。操作完成后。 我已经实现了该类的我自己的改编,该改编在下一个代码清单中显示。

ClassLoaderSwitcher.java

package dustin.examples.classloader;/*** Utility class for running operations on an explicitly specified class loader.*/
public class ClassLoaderSwitcher
{/*** Execute the specified action on the provided class loader.** @param classLoaderToSwitchTo Class loader from which the*    provided action should be executed.* @param actionToPerformOnProvidedClassLoader Action to be*    performed on the provided class loader.* @param <T> Type of Object returned by specified action method.* @return Object returned by the specified action method.*/public static <T> T executeActionOnSpecifiedClassLoader(final ClassLoader classLoaderToSwitchTo,final ExecutableAction<T> actionToPerformOnProvidedClassLoader){final ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();try{Thread.currentThread().setContextClassLoader(classLoaderToSwitchTo);return actionToPerformOnProvidedClassLoader.run();}finally{Thread.currentThread().setContextClassLoader(originalClassLoader);}}/*** Execute the specified action on the provided class loader.** @param classLoaderToSwitchTo Class loader from which the*    provided action should be executed.* @param actionToPerformOnProvidedClassLoader Action to be*    performed on the provided class loader.* @param <T> Type of Object returned by specified action method.* @return Object returned by the specified action method.* @throws Exception Exception that might be thrown by the*    specified action.*/public static <T> T executeActionOnSpecifiedClassLoader(final ClassLoader classLoaderToSwitchTo,final ExecutableExceptionableAction<T> actionToPerformOnProvidedClassLoader) throws Exception{final ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();try{Thread.currentThread().setContextClassLoader(classLoaderToSwitchTo);return actionToPerformOnProvidedClassLoader.run();}finally{Thread.currentThread().setContextClassLoader(originalClassLoader);}}
}

ClassLoaderSwitcher类上定义的两个方法每个都将接口作为其参数之一,并带有指定的类加载器。 接口使用run()方法指定一个对象,并且将针对提供的类加载器执行run()方法。 接下来的两个代码清单显示接口ExecutableActionExecutableExceptionableAction

ExecutableAction.java

package dustin.examples.classloader;/*** Encapsulates action to be executed.*/
public interface ExecutableAction<T>
{/*** Execute the operation.** @return Optional value returned by this operation;*    implementations should document what, if anything,*    is returned by implementations of this method.*/T run();
}

ExecutableExceptionableAction.java

package dustin.examples.classloader;/*** Describes action to be executed that is declared* to throw a checked exception.*/
public interface ExecutableExceptionableAction<T>
{/*** Execute the operation.** @return Optional value returned by this operation;*    implementations should document what, if anything,*    is returned by implementations of this method.* @throws Exception that might be possibly thrown by this*    operation.*/T run() throws Exception;
}

调用ClassLoaderSwitcher类上定义的方法的客户端不一定比执行临时上下文类加载器自身切换时要少一些代码,但是使用这样的通用类可确保始终更改上下文类加载器返回到原始类加载器,从而使开发人员无需确保可以进行重置,并防止“重置”在某个时候被无意中删除或在过程中的某个时候移得太晚。

需要为操作临时更改上下文类加载器的客户端可能会这样做,如下所示:

临时直接将ClassLoader切换为执行动作

final ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
try
{Thread.currentThread().setContextClassLoader(BundleActivator.class.getClassLoader());final String returnedClassLoaderString =String.valueOf(Thread.currentThread().getContextClassLoader())
}
finally
{Thread.currentThread().setContextClassLoader(originalClassLoader);
}

没有太多的代码行,但是必须记住要重置上下文类加载器为其原始类加载器。 接下来演示如何使用ClassLoaderSwitcher实用工具类执行相同的操作。

使用ClassLoaderSwitcher切换类加载器以执行操作(JDK 8之前的版本)

final String returnedClassLoaderString = ClassLoaderSwitcher.executeActionOnSpecifiedClassLoader(BundleActivator.class.getClassLoader(),new ExecutableAction<String>(){@Overridepublic String run(){return String.valueOf(Thread.currentThread().getContextClassLoader());}});

最后一个例子并不比第一个例子短,但是开发人员无需担心在第二个例子中显式地重置上下文类加载器。 请注意,这两个示例引用BundleActivator来在OSGi应用程序中获取Activator / System类加载器。 这就是我在这里使用的,但是可以在此处使用任何在适当的类加载器上加载的类,而不是BundleActivator 。 需要注意的另一件事是,我的示例使用了一个非常简单的操作,该操作在指定的类加载器上执行(返回当前线程上下文类加载器的String表示形式),在这里效果很好,因为它使我很容易看到指定的类加载器是用过的。 在实际情况下,此方法可以是在指定的类加载器上运行所需的任何方法。

如果我在指定的类加载器上调用的方法抛出一个已检查的异常,则可以使用ClassLoaderSwitcher提供的另一个重载方法(同名)来运行该方法。 下一个代码清单对此进行了演示。

将ClassLoaderSwitcher与可能引发检查异常的方法一起使用(JDK 8之前的版本)

String returnedClassLoaderString = null;
try
{returnedClassLoaderString = ClassLoaderSwitcher.executeActionOnSpecifiedClassLoader(BundleActivator.class.getClassLoader(),new ExecutableExceptionableAction<String>(){@Overridepublic String run() throws Exception{return mightThrowException();}});
}
catch (Exception exception)
{System.out.println("Exception thrown while trying to run action.");
}

使用JDK 8,我们可以使客户端代码更加简洁。 接下来的两个代码清单包含与前面两个代码清单中显示的方法相对应的方法,但已更改为JDK 8样式。

使用ClassLoaderSwitcher切换类加载器以执行动作(JDK 8样式)

final String returnedClassLoaderString = ClassLoaderSwitcher.executeActionOnSpecifiedClassLoader(urlClassLoader,(ExecutableAction<String>) () ->{return String.valueOf(Thread.currentThread().getContextClassLoader());});

将ClassLoaderSwitcher与可能引发检查异常的方法一起使用(JDK 8样式)

String returnedClassLoaderString = null;
try
{returnedClassLoaderString = ClassLoaderSwitcher.executeActionOnSpecifiedClassLoader(urlClassLoader,(ExecutableExceptionableAction<String>) () -> {return mightThrowException();});
}
catch (Exception exception)
{System.out.println("Exception thrown while trying to run action.");
}

与直接设置和重置上下文类加载器相比, JDK 8的lambda表达式使使用ClassLoaderSwitcher的客户端代码更加简洁(并且可以说更具可读性),同时通过确保始终将上下文类加载器切换回其来提供更高的安全性。原始类加载器。

结论

尽管无疑最好避免尽可能多地切换上下文类加载器,但是有时您可能没有其他合理的选择。 在那些时候,将开关中涉及的多个步骤封装起来,然后切换回一个可以由客户端调用的方法,可以增加操作的安全性,并且如果使用JDK 8编写,甚至可以使客户端拥有更简洁的代码。

其他参考

在本文中已经提到了其中一些参考,甚至对其进行了重点介绍,但为方便起见,在此再次将其包括在内。

  • GitHub上此博客文章中完整类的源代码 (不同的包名称)
  • OSGi联盟: 关于类加载器的知识
  • Neil Bartlett: 可怕的线程上下文类加载器
  • 纯粹的危险: 两个类加载器的故事
  • 信息矿山: OSGi类加载
  • JNDI教程:类加载
  • Adobe:OSGi中的类加载器问题 使用Thread上下文的第三方库
  • 揭秘Java类加载
  • Knopflerfish 5.2.0文档: 编程Knopflerfish:设置上下文类加载器
  • Knopflerfish 5.2.0 Javadoc: org.knopflerfish.util.ClassLoaderUtil
  • JavaWorld: 从ClassLoader迷宫中寻找出路
  • 技术与达尔文尼亚: Java ClassLoader和Context ClassLoader
  • Impala博客: 在多模块环境中使用线程的上下文类加载器

翻译自: https://www.javacodegeeks.com/2016/08/remembering-reset-thread-context-class-loader.html

记住要重置线程上下文类加载器相关推荐

  1. 重置线程中断状态_记住要重置线程上下文类加载器

    重置线程中断状态 我很难思考与Java 加载有关的东西,而不是与类加载器有关的东西. 在使用应用程序服务器或OSGi的情况下尤其如此,在这些应用程序服务器或OSGi中,经常使用多个类加载器,并且透明地 ...

  2. 利用classloader同一个项目中加载另一个同名的类_线程上下文类加载器ContextClassLoader内存泄漏隐患...

    前提 今天(2020-01-18)在编写Netty相关代码的时候,从Netty源码中的ThreadDeathWatcher和GlobalEventExecutor追溯到两个和线程上下文类加载器Cont ...

  3. 真正理解线程上下文类加载器(多案例分析)

    1.线程上下文类加载器是从jdk1.2开始引入的,类Thread中的getContextClassLoader()与setContextClassLoader(ClassLoader c1),分别用来 ...

  4. java查看上下文加载器_线程上下文类加载器

    package util.tom; import java.io.*; public class ThreadClassLoader extends Thread { @Override public ...

  5. JVM类加载理解(线程上下文类加载器、Tomcat类加载器)

    类加载机制概念 Java虚拟机把描述类的class文件加载到内存,对其进行校验.转换解析.初始化等操作,最终得到可以被虚拟机直接使用的java类型,这就是虚拟机的加载机制. 主要有五个步骤: 加载 将 ...

  6. (二)JVM成神路之剖析Java类加载子系统、双亲委派机制及线程上下文类加载器

    引言 上篇<初识Java虚拟机>文章中曾提及到:我们所编写的Java代码经过编译之后,会生成对应的class字节码文件,而在程序启动时会通过类加载子系统将这些字节码文件先装载进内存,然后再 ...

  7. java 上下文加载器_【深入理解Java虚拟机 】线程的上下文类加载器

    线程上下文类加载器 线程上下文类加载器( Thread Context ClassLoader) 是从JDK1.2 引入的,类Thread 的getContextClassLoader() 与 set ...

  8. Java高并发编程详解系列-线程上下文类加载

    前面的分享中提到的最多的概念就是关于类加载器的概念,但是当我们查看Thread源码的时候会发现如下的两个方法,这两个方法就是获取或者设置线程的上下文类加载器的方法,那么为什么要设置这两个方法呢?这个就 ...

  9. java 上下文加载器_如何将JDK6 ToolProvider和JavaCompiler与上下文类加载器一起使用?...

    我的用例是使用JDK 6中提供的ToolProvider和 JavaCompiler类从java程序编译生成的源文件.源文件包含对上下文类加载器中的类的引用(它在J2EE容器中运行),但不包含在系统类 ...

最新文章

  1. 如何扩大以太坊的规模:分片简介(How to Scale Ethereum: Sharding Explained)
  2. Python3 中 爬网页 \uxxx 问题
  3. UNITY2018.3 在editor下运行时new memoryprofiler显示 shader占用内存很大的问题在安卓上并没有看到...
  4. Eclipse 设置自动导包
  5. 乔布斯,影响了一个时代的人
  6. C语言 文件读写 ferror 函数 - C语言零基础入门教程
  7. php旅行社网站源码在线支付,PHP各大支付平台在线支付集成源码
  8. SAP License:ERP系统会计凭证中的那些必填项
  9. Mybatis 查询返回List<String>集合
  10. 企业网管服务器架设资料(极品中的极品)
  11. java递归求和 1 n_Java递归求和1+2+3+...+n实例详解
  12. java sqlite 多线程_深入Sqlite多线程入库的问题
  13. UITextField的leftView
  14. 5G+北斗RTK定位:高精度定位技术发展更进一步
  15. java 拼音_JAVA实现汉字转换为拼音 pinyin4j/JPinyin
  16. hiberfil.sys和swapfile.sys文件的删除
  17. 2D基本知识入门学习
  18. 从扫码登录的原理分析QQ大量被盗事件
  19. 语音计算机怎么切换音乐模式,如何把微信里收藏的语音音乐转换成mp3格式?
  20. 2021安徽省安全员B证 多选题考试题库及答案

热门文章

  1. 架构必备「RESTful API」设计技巧经验总结
  2. JVM菜鸟进阶高手之路
  3. 204787 ,194787 |0001 1131 0001 4226 7035 ![2480 ]
  4. ssm使用全注解实现增删改查案例——EmpServiceImpl
  5. ssm使用全注解实现增删改查案例——Dept
  6. sql server链接查询
  7. 图像sobel梯度详细计算过程_数字图像处理(第十章)
  8. ReviewForJob——最小生成树(prim + kruskal)源码实现和分析
  9. ReviewForJob——堆排序
  10. 《线性代数及其应用》