深入理解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
- // Now create the class loader to use to launch the application
- try
- loader = AppClassLoader.getAppClassLoader(extcl);
- catch (IOException e)
- throw new InternalError(
- "Could not create application class loader" );
- // Also set the context class loader for the primordial thread.
- 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
- package classloader;
- import java.net.URL;
- import java.net.URLClassLoader;
- /**
- * 可以重新载入同名类的类加载器实现
- * 放弃了双亲委派的加载链模式,需要外部维护重载后的类的成员变量状态
- */
- public class HotSwapClassLoader extends URLClassLoader
- public HotSwapClassLoader(URL[] urls)
- super(urls);
- public HotSwapClassLoader(URL[] urls, ClassLoader parent)
- super(urls, parent);
- // 下面的两个重载load方法实现类的加载,仿照ClassLoader中的两个loadClass()
- // 具体的加载过程代理给父类中的相应方法来完成
- public Class<?> load(String name) throws ClassNotFoundException
- return load(name, false);
- public Class<?> load(String name, boolean resolve) throws ClassNotFoundException
- // 若类已经被加载,则重新再加载一次
- if (null != super.findLoadedClass(name))
- return reload(name, resolve);
- // 否则用findClass()首次加载它
- Class<?> clazz = super.findClass(name);
- if (resolve)
- super.resolveClass(clazz);
- return clazz;
- public Class<?> reload(String name, boolean resolve) throws ClassNotFoundException
- return new HotSwapClassLoader(super.getURLs(), super.getParent()).load(
- name, resolve);
下面是使用示例:
[java] view plain copy
- package classloader;
- public class A
- private B b;
- public void setB(B b)
- this.b = b;
- public B getB()
- return b;
- package classloader;
- public class B
- package classloader;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- import java.net.MalformedURLException;
- import java.net.URL;
- public class TestHotSwap
- public static void main(String args[]) throws MalformedURLException
- A a = new A(); // 加载类A
- B b = new B(); // 加载类B
- a.setB(b); // A引用了B,把b对象拷贝到A.b
- 真正理解线程上下文类加载器(多案例分析)