重置线程中断状态_记住要重置线程上下文类加载器
重置線程中斷狀態
我很難思考與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()方法。 接下來的兩個代碼清單顯示接口ExecutableAction和ExecutableExceptionableAction 。
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
重置線程中斷狀態
總結
以上是生活随笔為你收集整理的重置线程中断状态_记住要重置线程上下文类加载器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Gradle技巧–显示buildscri
- 下一篇: 黄金收储备案流程(黄金收储备案)