记住要重置线程上下文类加载器

我很难思考与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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/351219.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

EntityFramework Code-First—领域类配置之DataAnnotations

本文出自&#xff1a;https://www.cnblogs.com/tang-tang/p/5510574.html 一、摘要 EF Code-First提供了一个可以用在领域类或其属性上的DataAnnotation特性集合&#xff0c;DataAnnotation特性会覆盖默认的EF约定。 DataAnnotation存在于两个命名空间里&#xff1a; System.Co…

Python 调试工具 PDB(Linux 环境下调试)

转载&#xff1a;http://blog.163.com/gjx0619126/blog/static/12740839320114995947700/ 在python中使用pdb模块可以进行调试 import pdb pdb.set_trace() 也可以使用python -m pdb mysqcript.py这样的方式 (Pdb) 会自动停在第一行&#xff0c;等待调试,这时你可以看看 帮助…

Ubuntu 更新源方法

安装完Ubuntu系统之后&#xff0c;面临的最主要的一个问题就是将apt安装源进行更新&#xff0c;因为在国内直接利用Ubuntu默认的安装源下载安装包速度慢&#xff0c;并且有的时候软件版本也比较旧。今天小编对Ubuntu更新源进行介绍&#xff1a;&#xff08;这里针对阿里源和清华…

PAT Basic 1002

1002 写出这个数 &#xff08;20 分&#xff09;读入一个正整数 n&#xff0c;计算其各位数字之和&#xff0c;用汉语拼音写出和的每一位数字。 输入格式&#xff1a; 每个测试输入包含 1 个测试用例&#xff0c;即给出自然数 n 的值。这里保证 n 小于 10​100​​。 输出格式&…

mybatis crud_MyBatis教程– CRUD操作和映射关系–第2部分

mybatis crud为了说明这一点&#xff0c;我们正在考虑以下示例域模型&#xff1a; 会有用户&#xff0c;每个用户可能都有一个博客&#xff0c;每个博客可以包含零个或多个帖子。 这三个表的数据库结构如下&#xff1a; CREATE TABLE user (user_id int(10) unsigned NOT NU…

MATLAB 排序函数(先按第一列排序(主排序)然后再按第二列排序(次排序))

利用 sortrows 函数实现MATLAB 先按第一列排序&#xff08;主排序&#xff09;然后再按第二列排序&#xff08;次排序&#xff09; A [8,9,6;5,5,2;2,5,8] sortrows(A)A 8 9 65 5 22 5 8ans 2 5 85 5 28 9 6

用PDB库调试Python程序

Python自带的pdb库&#xff0c;发现用pdb来调试程序还是很方便的&#xff0c;当然了&#xff0c;什么远程调试&#xff0c;多线程之类&#xff0c;pdb是搞不定的。用pdb调试有多种方式可选&#xff1a;1. 命令行启动目标程序&#xff0c;加上-m参数&#xff0c;这样调用myscrip…

数据操作

mysql> create table employee(-> id int primary key auto_increment,-> emp_name char(12) not null,-> sex enum(male,female) not null default male, #大部分是男的-> age int(3) unsigned not null default 28,-> hire_date date not null,-> post …

/usr/bin/ld: cannot find -l*** 这里***可以指lapack等

在Linux安装编译过程中有时会出现在如下形式的错误&#xff1a; /usr/bin/ld: cannot find -l***这里表示编译过程中找不到以下库名&#xff1a; lib库名(即***).so会发生这样的原因有以下三种情形&#xff1a; 系统没有安装相对应的lib 相对应的lib版本不对 lib&#xff0…

通过分区在Kafka中实现订单保证人

Kafka最重要的功能之一是实现消息的负载平衡&#xff0c;并保证分布式集群中的排序&#xff0c;否则传统队列中将无法实现。 首先让我们尝试了解问题陈述 让我们假设我们有一个主题&#xff0c;其中发送消息&#xff0c;并且有一个消费者正在使用这些消息。 如果只有一个使用…

破解栅栏密码python脚本

今天遇到一个要破解的栅栏密码&#xff0c;写了个通用的脚本 1 #!/usr/bin/env python2 # -*- coding: gbk -*-3 # -*- coding: utf_8 -*-4 # Author: 蔚蓝行5 # http://www.cnblogs.com/duanv6 e raw_input(请输入要解密的字符串\n)7 elen len(e)8 field[]9 for i in range(…

水稻已知os基因号,利用DAVIA进行GO功能富集分析

第1-5步&#xff1a; 已知水稻的基因&#xff08;os&#xff09;&#xff0c;进行功能注释 第6步 第七步&#xff1a; 第八步&#xff1a; 第九步&#xff1a; 第十步&#xff1a; 第十一步&#xff1a;

二,八,十,十六进制之间转换的相应方法

int num1 Integer.valueOf(n,16); //16进制转换成10进制 Integer.toHexString(Integer i); //10进制转换成16进制 补充&#xff1a;Integer.toHexString(Integer i);该方法得出的字符默认为小写&#xff0c;如果想得到大写结果&#xff0c;则变为Integer.toHexString(Integer i…

IDF实验室-图片里的英语

原题&#xff1a; 一恒河沙中有三千世界&#xff0c;一张图里也可以有很多东西。 不多说了&#xff0c;答案是这个图片包含的那句英文的所有单词的首字母。 首字母中的首字母要大写&#xff0c;答案格式是wctf{一坨首字母} 加油吧少年&#xff01;看好你哦~ writeup&#xff…

linux 终端调用MATLAB程序

linux 终端调用MATLAB程序 路径&#xff1a;/A/B/C/ 程序名称&#xff1a;xxx.m linux 终端调用MATLAB函数方法 cd /A/B/C/ matlab -nodisplay -nosplash -nodesktop -r "xxx;exit;"

2018-11-02 在代码中进行中文命名实践的短期目标

对中文命名的意义不再赘述, 请参看之前的对在代码中使用中文命名的质疑与回应. 去年中文命名实践的阻力和应对之后, 在一些小项目中继续实践了中文命名(Java/JS/Python等, 详见之前的专栏文章), 涉及领域不少但尚未形成明确的重点项目. 发现了一些在业务相关代码使用中文命名的…

Wireshark 命令行捕获数据

在 Wireshark 程序目录中&#xff0c;包含两个命令行捕获工具。这两个工具分别是 Dumpcap 和 Tshark。当不能以图形界面方式捕获数据时&#xff0c;可以在命令行使用 dumpcap 或 tshark 程序实施捕获。 一、使用 Dumpcap 捕获数据 执行 dumpcap -h 可以查看参数详情。 1、执行 …

zk ui_高级ZK:异步UI更新和后台处理–第2部分

zk ui介绍 在第1部分中&#xff0c;我展示了如何在ZK应用程序中使用服务器推送和线程来执行后台任务。 但是&#xff0c;这个简单的示例具有一个重大缺陷&#xff0c;这使其对于实际应用程序而言是一种不好的方法&#xff1a;它为每个后台任务启动了一个新线程。 JDK5引入了E…

学生管理系统 数据库版结果 查询student表中所有学生信息

1.创建school_java数据库 CREATE DATABASE schooljava; USE schooljava; CREATE TABLE student ( id INT(11), name VARCHAR(25), tel INT(11), sex VARCHAR(6) ); DESC student; java代码 package Mysql; import java.sql.Connection; import java.sql.DriverManager; imp…

如何查看思科交换机的出厂时间?

1.在交换机命令行运行show version 查看交换机的sn码 System serial number : FOC1723W0VP 2.SN码取出第四位至七位 以 FOC1723W0VP 为例 第四和第五位代表年份&#xff0c;第六和第七位代表当年的第XX周 &#xff08;范围是01至52周&#xff09; 1719962013 ; 23周 (注&…