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

重置线程中断状态

我很难思考与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>()
   {
      @Override
      public 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>()
      {
         @Override
         public 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 8lambda表达式使使用ClassLoaderSwitcher的客户端代码更简洁(并且可以说更具可读性),同时通过确保始终将上下文类加载器切换回其来提供更高的安全性。原始类加载器。

结论

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

其他参考

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

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

重置线程中断状态

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值