主流的Java Web服务器,如Tomcat、Jetty、WebLogic、WebSphere或其他服务器,都实现了自己定义的类加载器(一般都不止一个)。因为一个功能健全的Web服务器,要解决如下几个问题:
部署在同一个服务器上的两个Web应用程序所使用的Java类库相互隔离。这是最基本的需求,两个不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求一个类库在一个服务器中只有一份,服务器应当保证两个应用程序的类库可以互相独立使用。
部署在同一个服务器上的两个web程序所使用的Java类库可以互相共享。例如,用户可能有10个使用Spring的应用程序部署在一台服务器中,如果把10份Spring分别存放在各个应用程序的隔离目录中,将会是很大的资源浪费,这倒不是浪费磁盘空间,而是指类库在使用时都要被加载到服务器内存,如果类库不能共享,虚拟机的方法区很容易出现过度膨胀的风险。
服务器需要尽可能的保证自身安全不受部署的Web应用程序影响。目前,许多主流的Java Web服务器自身也是用Java语言来实现。因此服务器本身也有类库依赖问题,一般来说,基于安全考虑,服务器所使用的类库应该与应用程序的类库互相独立。
支持JSP应用的Web服务器,大多数都需要支持HotSwap功能(热拔插)。主流的web服务器都会支持JSP生成类的热替换。
由于存在上述问题,在部署Web应用时,单独的一个classPath就无法满足需求了,所以各种Web服务器都不约而同地提供了好几个ClassPath路径供用户存放第三方类库,这些路径一般都以lib或classes命名。被放置到不同路径中的类库,具备不用的访问范围和服务对象,通常,每一个目录都会有一个相应的自定义类加载器去加载放置在里面的Java类库。现在以Tomcat为例,看一看Tomcat如何规划用户类库结构与类加载器的。
在Tomcat6之前的目录结构中,有三组结构(/common/* /server/* /shared/*)可以存放Java类库,另外加上Web应用程序自身的目录/WEB-INF/*,一共4组,把Java类库放置在这些目录中的含义分别如下:
放置在/common目录中:类库可被Tomcat和所有Web应用程序共同使用。
放置在/server目录中:类库可被Tomcat使用,对所有Web应用程序都不可见。
放置在/shared目录中:类库可以被所有的Web应用程序共同使用,但是对Tomcat自己不可见。
放置在/WebAPP/WEB-INF目录中:类库仅仅可以被此Web应用程序使用,对Tomcat与其他Web应用程序不可见。
为了支持这套目录结构,Tomcat自定义了多个类加载器,这些类加载器按照经典的双亲委派模型来实现:
其中Webapp类加载和Jsp类加载器通常会存在多个实例,每一个Web应用程序对应一个WebApp类加载器,每一个JSP文件对应一个Jsp类加载器。从上图中发现,commonclassloader能加载的类都可以被CatalinaClassLoader和ShareClassLoader使用,而CatalinaClassLader和SharedClassLoader自己能加载的类则和对方互相隔离。WebAppClassLoader可以使用Sharedclassloader加载到的类,但各个webappclassloader实例之间相互隔离。而Jsploader的加载范围仅仅是这个JSP文件编译出来的那一个class,他出现的目的就是被抛弃:当服务器检查到JSP文件被修改时,会替换掉目前的jasperloader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap。
对于Tomcat6.x及以后的版本,只有指定了tomcat/conf/catalina/properties配置文件的server-loader和share.lader项后才会真正建立Catalinaclassloader和shareclassloader的实例,否则会用到这两个类加载器的地方都会用commonclassloader实例代替,而默认配置文件中没有设置这两个loader项,所以tomcat顺理成章把/common /server /shared三个目录默认合并到一起变成一个/lib目录,这个目录里的类库相当于以前/common目录中类库的作用。这是Tomcat设计团队为了简化大多数部署场景所做的一改进,如果默认设置不能满足需求,用户可以修改配置文件重新启用Tomcat5的加载器架构。
Tomcat加载器清晰易懂,并且采用了官方推荐的正统的使用类加载器的方式。提个问题,如果有10个web应用程序都使用Spring进行组织和管理,可以把Spirng放到common或shared目录下让这些程序共享。Spring要对用户程序的类进行管理,自然要能访问到用户程序的类,而用户的程序显然是放在/wabapp/WEB-INF目录中的,那么被commonclassloader或sharedclassloader加载的Spirng如何访问并不在加载范围内的用户程序呢,可以使用线程上下文类加载器
tomcat有没有破坏双亲委派模型?
双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应当由自己的父类加载器加载。很显然,tomcat 不是这样实现,tomcat 为了实现隔离性,没有遵守这个约定,每个webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器。