实际上,在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
https://www.ibm.com/developerworks/cn/java/j-lo-classloader/