《深入理解Java虚拟机:JVM高级特性与最佳实践》一书中,有这样一段:
双亲委派模型的第二次“被破坏”是由这个模型自身的缺陷所导致的,双亲委派很好地解决了各个类加载器的基础类的统一问题(越基础的类由越上层的加载器进行加载),基础类之所以称为“基础”,是因为它们总是作为被用户代码调用的API,但世事往往没有绝对的完美,如果基础类又要调用回用户的代码,那该怎么办?
对于这一段话一直不是很理解,上层加载器加载的基础类为什么要回调下层加载器加载的代码?类加载机制更全面一点的描述可以是全盘负责与双亲委托,这样可能更好理解上面的话。
全盘负责:是指当一个ClassLoader装载一个类时,除非显示地使用另一个ClassLoader,则该类所依赖及引用的类也由这个CladdLoader载入。
双亲委派:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
通过上面两个原则,上层加载器依赖及引用的类默认情况下也要该类加载器加载,比如连接数据库时使用JDBC。JDBC依赖具体的实现类,但是JDBC是上层类加载器加载完成的,他要加载下层类加载器才能加载的类是无法完成的,此时就需要在调用回用户的代码。
再来看书里的举出的一个例子JNDI服务,JNDI现在已经是Java的标准服务,它的代码由启动类加载器去加载(在JDK1.3时放进去的rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者(SPI,Service Provider Interface)的代码,启动类加载器加载JNDI类时,根据全盘负责机制,还需要加载JNDI引用的ClassPath下的类,但是此时根据双亲委派机制同样会造成无法加载。
为了解决上述问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器,此时便可以通过获得线程上下文类加载器,从而使用应用程序类加载器加载ClassPath下的类,与此同时,双亲委派模型也被破坏了。
后面书中提到的:
如果有10个Web应用程序都是用Spring来进行组织和管理的话,可以把Spring放到Common或Shared目录下让这些程序共享。Spring要对用户程序的类进行管理,自然要能访问到用户程序的类,而用户的程序显然是放在/WebApp/WEB-INF目录中的,那么被CommonClassLoader或SharedClassLoader加载的Spring如何访问并不在其加载范围内的用户程序呢?
这个应该也可以很容易明白Spring通过使用线程上下文类加载器的机制完成上述功能。