Classloader

ClassLoader,Thread.currentThread().setContextClassLoader,tomcat的ClassLoader

2014-05-07 21:41  Loull  阅读(6776)  评论(0)  编辑 收藏html

实际上,在Java应用中全部程序都运行在线程里,若是在程序中没有手工设置过ClassLoader,对于通常的java类以下两种方法得到的ClassLoader一般都是同一个 java

this.getClass.getClassLoader();  
Thread.currentThread().getContextClassLoader();  

方法一获得的Classloader是静态的,代表类的载入者是谁;c++

方法二获得的Classloader是动态的,谁执行(某个线程),就是那个执行者的Classloader。对于单例模式的类,静态类等,载入一次后,这个实例会被不少程序(线程)调用,对于这些类,载入的Classloader和执行线程的Classloader一般都不一样。web

 

1、线程上下文类加载器

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

  前面提到的类加载器的代理模式并不能解决 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 的核心库。它也不能代理给系统类加载器,由于它是系统类加载器的祖先类加载器。也就是说,类加载器的代理模式没法解决这个问题。bootstrap

  线程上下文类加载器正好解决了这个问题。若是不作任何的设置,Java 应用的线程的上下文类加载器默认就是系统上下文类加载器。在 SPI 接口的代码中使用线程上下文类加载器,就能够成功的加载到 SPI 实现的类。线程上下文类加载器在不少 SPI 的实现中都会用到。tomcat

JNDI,JDBC的诉求是:安全

  为了能让应用程序访问到这些jar包中的实现类,即用appClassLoarder去加载这些实现类。能够用getContextClassLoader取得当前线程的ClassLoader(即appClassLoarder),而后去加载这些实现类,就能让应用访问到app

tomcat的诉求:webapp

  稍微跟上面有些不一样,容器不但愿它下面的webapps之间能互相访问到,因此不能用appClassLoarder去加载。因此tomcat新建一个sharedClassLoader(它的parent是commonClassLoader,commonClassLoader的parent是appClassLoarder,默认状况下,sharedClassLoader和commonClassLoader是同一个UrlClassLoader实例),这是catalina容器使用的ClassLoader。对于每一个webapp,为其新建一个webappClassLoader,用于加载webapp下面的类,这样webapp之间就不能相互访问了。tomcat的ClassLoader不彻底遵循双亲委派,首先用webappClassLoader去加载某个类,若是找不到,再交给parent。而对于java核心库,不在tomcat的ClassLoader的加载范围。

  看下tomcat的Bootstrap类的init方法:

复制代码
public void init() throws Exception {

        initClassLoaders();

        Thread.currentThread().setContextClassLoader(catalinaLoader);//不知道这行设置了以后,对后面有什么用???

        SecurityClassLoad.securityClassLoad(catalinaLoader);

        // Load our startup class and call its process() method/*反射实例化Catalina类的实例*/
        Class<?> startupClass =
            catalinaLoader.loadClass
            ("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.newInstance();// Set the shared extensions class loader
        if (log.isDebugEnabled())
            log.debug("Setting startup class properties");
        String methodName = "setParentClassLoader";
        Class<?> paramTypes[] = new Class[1];
        paramTypes[0] = Class.forName("java.lang.ClassLoader");
        Object paramValues[] = new Object[1];
        paramValues[0] = sharedLoader;
        Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
        method.invoke(startupInstance, paramValues);

        catalinaDaemon = startupInstance;

    }
复制代码

 

  因为Bootstrap类和catalina类被发布在不一样包里面,Bootstrap对catalina实例的操做必须用反射完成。

  catalina类实例(即startupClass)由反射生成,它的ClassLoader是catalinaLoader。而后反射调用方法setParentClassLoader设置catalina类实例里面的变量parentClassLoader为sharedClassLoader,意思是做为容器下webapp的webappClassLoader的parent,而不是设置catalina类的ClassLoader的parent是sharedClassLoader。

  如今对tomcat的Bootstrap类的init方法里面的Thread.currentThread().setContextClassLoader(catalinaLoader);这一行仍是很疑惑。由于,在类catalina里面,能够用getClass().getClassLoader()获取catalinaClassLoader,不须要从Thread.currentThread().getContextClassLoader()方法得到。难道是为了让子线程的ClassLoader都是catalinaClassLoader,而不是appClassLoarder??

 

2、类加载器与 Web 容器

  对于运行在 Java EE™容器中的 Web 应用来讲,类加载器的实现方式与通常的 Java 应用有所不一样。不一样的 Web 容器的实现方式也会有所不一样。以 Apache Tomcat 来讲,每一个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模式,所不一样的是它是首先尝试去加载某个类,若是找不到再代理给父类加载器。这与通常类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐作法,其目的是使得 Web 应用本身的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java 核心库的类是不在查找范围以内的。这也是为了保证 Java 核心库的类型安全。

  绝大多数状况下,Web 应用的开发人员不须要考虑与类加载器相关的细节。下面给出几条简单的原则:

  • 每一个 Web 应用本身的 Java 类文件和使用的库的 jar 包,分别放在 WEB-INF/classes和 WEB-INF/lib目录下面。
  • 多个应用共享的 Java 类文件和 jar 包,分别放在 Web 容器指定的由全部 Web 应用共享的目录下面。
  • 当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确。

 

3、ContextClassLoader和其余ClassLoader的关系 

  咱们能够经过getContextClassLoader方法来得到此context classloader,就能够用它来载入咱们所须要的Class。默认的是system classloader。

  bootstrap classloader  -------  对应jvm中某c++写的dll类
  Extenson ClassLoader ---------对应内部类ExtClassLoader
  System ClassLoader  ---------对应内部类AppClassLoader
  Custom ClassLoader  ----------对应任何URLClassLoader的子类(你也能够继承SecureClassLoader或者更加nb一点 直接继承ClassLoader,这样的话你也是神通常的存在了 XD)

  以上四种classloder按照从上到下的顺序,依次为下一个的parent

  这个第一律念

  第二个概念是几个有关的classloader的类

           抽象类 ClassLoader
                  |
            SecureClassLoader
                   |
            URLClassloader
             |           |                
 sun的ExtClassLoader   sun的AppClassLoader
  以上的类之间是继承关系,与第一个概念说的parent是两回事情,须要当心。

  第三个概念是Thread的ContextClassLoader
  其实从Context的名称就能够看出来,这只是一个用以存储任何classloader引用的临时存储空间,与classloader的层次没有任何关系。

 

4、Context ClassLoader详解

  一般状况下,类装载器共有4种,即启动类装载器、EXT类装载器、App类装载器和自定义类装载器。他们之间的阶层状况以下图左面所示,他们都有着不一样的载入规则,而且经过向上代理的方式来进行。而本文所提到的Context Class Loader并非一种新的装载器类型,而是一种抽象的说法,它的具体表现形式为:调用Thread.getCurrentThread().getContextClassLoader()所返回的那个ClassLoader。它和JVM缺省的类装载器以及自定义类装载之间是什么关系呢?下面经过一个实验来看一下。

 

 

 

3 实战演练

(1)步骤一

  上图进行了这样一个实验:首先一个名为Class(1)的类中启动MainThread(其实就是这个类里面有main函数的意思啦),注意这个类的名字后面标出了其所在的路径(即ClassPath),而后在里面进行测试,发现目前它的装载器和当前线程(MainThread)的ContextClassLoader都是AppClassLoader。而后Class(1)启动了一个新线程Class(2)。这里的Class(2)是一个Thread的子类,执行Class(2)代码的线程我称之为Thread-0。

(2)步骤二

  上图能够看到Class(2)的装载器和ContextClassLoader一样都是AppClassLoader。随后我在Class(2)中建立了一个新的URLCLassLoader,并用这个ClassLoader来载入另外一个和Class(1)不在同一个ClassPath下的类Class(3)。此时咱们就能够看到变化:即载入Class(3)的装载器是URLClassLoader,而ContextClassLoader还仍然是AppClassLoader。

(2)步骤三

  最后咱们在Class(3)中启动了一个线程类Class(4),发现Class(4)也是由URLClassLoader载入的,而此时ContextClassLoader仍然是AppClassLoader。

    在整个过程当中,装载类的ClassLoader发生了变化,因为线程类Class(4)是由Class(3)启动的,因此装载它的类装载器就变成了URLClassLoader。与此同时,全部线程的ContextClassLoader都继承了生成该线程的ContextClassLoader--AppClassLoader。

 

  若是咱们在第二步的结尾执行了绿色框中的代码:setContextClassLoader(),则结果就会变成下面这个样子:

 

  咱们能够清楚地看到,因为Thread-0将其ContextClassLoader设置成了URLClassLoader,而Thread-1是在Thread-0里面生成的,因此就继承了其ContextClassLoader,变成了URLClassLoader。

 

3 后记

  这里列出的试验可能不见得全面,但相信足以说明问题,应该能够说明ContextClassLoader与其它类装载器的区别所在。但有可能ContextClassLoader还有其余的不一样之处,但愿有这方面经验的朋友一块儿讨论。

 

  Thread.currentThread().getContextClassLoader()的意义:

  父Classloader可使用当前线程Thread.currentthread().getContextLoader()中指定的classloader中加载的类。颠覆了父ClassLoader不能使用子Classloader或者是其它没有直接父子关系的Classloader中加载的类这种状况。这个就是Context Class Loader的意义。

 

5、Current ClassLoader

  当前类所属的ClassLoader,在虚拟机中类之间引用,默认就是使用这个ClassLoader。另外,当你使用Class.forName(), Class.getResource()这几个不带ClassLoader参数的方法时,默认一样使用当前类的ClassLoader。你能够经过方法XX.class.GetClassLoader()获取。

 

Reference

浅析Context Class Loader

ContextClassLoader浅析

https://www.ibm.com/developerworks/cn/java/j-lo-classloader/

http://blog.sina.com.cn/s/blog_605f5b4f01010i48.html

 http://my.oschina.net/u/571166/blog/212903

相关文章
相关标签/搜索