深入理解Java类加载器:线程上下文类加载器

Posted 好好学习312

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解Java类加载器:线程上下文类加载器相关的知识,希望对你有一定的参考价值。

1 线程上下文类加载器


  线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 Java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。

  前面提到的类加载器的代理模式并不能解决 Java 应用开发中会遇到的类加载器的全部问题。Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,如 JAXP 的 SPI 接口定义包含在 javax.xml.parsers包中。这些 SPI 的实现代码很可能是作为 Java 应用所依赖的 jar 包被包含进来,可以通过类路径(CLASSPATH)来找到,如实现了 JAXP SPI 的 Apache Xerces所包含的 jar 包。SPI 接口中的代码经常需要加载具体的实现类。如 JAXP 中的 javax.xml.parsers.DocumentBuilderFactory类中的 newInstance()方法用来生成一个新的 DocumentBuilderFactory的实例。这里的实例的真正的类是继承自 javax.xml.parsers.DocumentBuilderFactory,由 SPI 的实现所提供的。如在 Apache Xerces 中,实现的类是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl。而问题在于,SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的; SPI 实现的 Java 类一般是由系统类加载器来加载的。引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。也就是说,类加载器的代理模式无法解决这个问题。

  线程上下文类加载器正好解决了这个问题。如果不做任何的设置,Java 应用的线程的上下文类加载器默认就是系统上下文类加载器。在 SPI 接口的代码中使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。线程上下文类加载器在很多 SPI 的实现中都会用到。

  Java默认的线程上下文类加载器是系统类加载器(AppClassLoader)。以下代码摘自sun.misc.Launch的无参构造函数Launch()。

[java]  view plain  copy
  1. // Now create the class loader to use to launch the application  
  2. try   
  3.     loader = AppClassLoader.getAppClassLoader(extcl);  
  4.  catch (IOException e)   
  5.     throw new InternalError(  
  6. "Could not create application class loader" );  
  7.   
  8.   
  9. // Also set the context class loader for the primordial thread.  
  10. Thread.currentThread().setContextClassLoader(loader);  

  使用线程上下文类加载器,可以在执行线程中抛弃双亲委派加载链模式,使用线程上下文里的类加载器加载类。典型的例子有:通过线程上下文来加载第三方库jndi实现,而不依赖于双亲委派。大部分Java application服务器(jboss, tomcat..)也是采用contextClassLoader来处理web服务。还有一些采用hot swap特性的框架,也使用了线程上下文类加载器,比如 seasar (full stack framework in japenese)。
  线程上下文从根本解决了一般应用不能违背双亲委派模式的问题。使java类加载体系显得更灵活。随着多核时代的来临,相信多线程开发将会越来越多地进入程序员的实际编码过程中。因此,在编写基础设施时, 通过使用线程上下文来加载类,应该是一个很好的选择。
  当然,好东西都有利弊。使用线程上下文加载类,也要注意保证多个需要通信的线程间的类加载器应该是同一个,防止因为不同的类加载器导致类型转换异常(ClassCastException)。

  defineClass(String name, byte[] b, int off, int len,ProtectionDomain protectionDomain)是java.lang.Classloader提供给开发人员,用来自定义加载class的接口。使用该接口,可以动态的加载class文件。例如在jdk中,URLClassLoader是配合findClass方法来使用defineClass,可以从网络或硬盘上加载class。而使用类加载接口,并加上自己的实现逻辑,还可以定制出更多的高级特性。

  下面是一个简单的hot swap类加载器实现。hot swap即热插拔的意思,这里表示一个类已经被一个加载器加载了以后,在不卸载它的情况下重新再加载它一次。我们知道Java缺省的加载器对相同全名的类只会加载一次,以后直接从缓存中取这个Class object。因此要实现hot swap,必须在加载的那一刻进行拦截,先判断是否已经加载,若是则重新加载一次,否则直接首次加载它。我们从URLClassLoader继承,加载类的过程都代理给系统类加载器URLClassLoader中的相应方法来完成。

[java]  view plain  copy
  1. package classloader;  
  2.   
  3. import java.net.URL;  
  4. import java.net.URLClassLoader;  
  5.   
  6. /** 
  7.  * 可以重新载入同名类的类加载器实现 
  8.  * 放弃了双亲委派的加载链模式,需要外部维护重载后的类的成员变量状态 
  9.  */  
  10. public class HotSwapClassLoader extends URLClassLoader   
  11.   
  12.     public HotSwapClassLoader(URL[] urls)   
  13.         super(urls);  
  14.       
  15.   
  16.     public HotSwapClassLoader(URL[] urls, ClassLoader parent)   
  17.         super(urls, parent);  
  18.       
  19.   
  20.     // 下面的两个重载load方法实现类的加载,仿照ClassLoader中的两个loadClass()  
  21.     // 具体的加载过程代理给父类中的相应方法来完成  
  22.     public Class<?> load(String name) throws ClassNotFoundException   
  23.         return load(name, false);  
  24.       
  25.   
  26.     public Class<?> load(String name, boolean resolve) throws ClassNotFoundException   
  27.         // 若类已经被加载,则重新再加载一次  
  28.         if (null != super.findLoadedClass(name))   
  29.             return reload(name, resolve);  
  30.           
  31.         // 否则用findClass()首次加载它  
  32.         Class<?> clazz = super.findClass(name);  
  33.         if (resolve)   
  34.             super.resolveClass(clazz);  
  35.           
  36.         return clazz;  
  37.       
  38.   
  39.     public Class<?> reload(String name, boolean resolve) throws ClassNotFoundException   
  40.         return new HotSwapClassLoader(super.getURLs(), super.getParent()).load(  
  41.                 name, resolve);  
  42.       
  43.   
  两个重载的load方法参数与ClassLoader类中的两个loadClass()相似。在load的实现中,用findLoadedClass()查找指定的类是否已经被祖先加载器加载了,若已加载则重新再加载一次,从而放弃了双亲委派的方式(这种方式只会加载一次)。若没有加载则用自身的findClass()来首次加载它。

  下面是使用示例:

[java]  view plain  copy
  1. package classloader;  
  2.   
  3. public class A   
  4.       
  5.     private B b;  
  6.   
  7.     public void setB(B b)   
  8.         this.b = b;  
  9.       
  10.   
  11.     public B getB()   
  12.         return b;  
  13.       
  14.   
[java]  view plain  copy
  1. package classloader;  
  2.   
  3. public class B   
  4.       
  5.   
[java]  view plain  copy
  1. package classloader;  
  2.   
  3. import java.lang.reflect.InvocationTargetException;  
  4. import java.lang.reflect.Method;  
  5. import java.net.MalformedURLException;  
  6. import java.net.URL;  
  7.   
  8. public class TestHotSwap   
  9.   
  10.     public static void main(String args[]) throws MalformedURLException   
  11.         A a = new A();  // 加载类A  
  12.         B b = new B();  // 加载类B  
  13.         a.setB(b);  // A引用了B,把b对象拷贝到A.b  
  14. 真正理解线程上下文类加载器(多案例分析)

    深入拆解类加载器,这样的姿势你还不懂吗?

    深入理解Java类加载器:Java类加载原理解析

    深入理解Java类加载器

    java中类加载器ClassLoader,双亲加载机制,启动类加载器,应用类加载器,线程上下文类加载器

    深入理解Java虚拟机——类加载器