一.要解决的问题?
- 部署在同一个服务器上的两个Web应用程序所使用的的Java类库可以实现相互隔离。这是最基本的要求,两个不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求一个类库在服务器中只有一份,服务器应当保证两个应用程序的类库可以互相独立使用。
- 部署在同一个服务器上的两个Web应用程序所使用的Java类库可以共享。这个需求也很常见,例如,用户可能有10个使用Spring组织的应用程序部署在同一台服务器上,如果把10份Spring分别存放在各个应用程序的隔离目录中,将会是很大的资源浪费--这主要倒不是浪费磁盘空间的问题,而是指类库在使用时都要被加载到服务器内存,如果类库不能被共享,虚拟机的方法区就会很容易出现过度膨胀的风险。
- 服务器需要尽可能的保证自身的安全不受部署的Web应用程序影响。目前,有许多主流的Java Web服务器自身也是使用Java语言来实现的。因此,服务器本身也有类库依赖的问题,一般来说,基于安全考虑,服务器所使用的类库应该与应用程序的类库相互独立。
- 支持JSP应用的Web服务器,大多数都需要支持HotSwap功能。JSP文件最终是要编译成Java Class才能由虚拟机执行,但JSP文件由于其纯文本存储的特性,运行时修改的概率远远大于第三方类库或程序自身的Class文件。
由于存在上述问题,在部署Web应用时,单独一个ClassPath就无法满足需求,所以各种Web服务器都“不约而同”地提供好几个ClassPath路径供用户存放第三方类库,这些路径一般都以“lib”或“classes”命名。被放置到不同路径的类库,具备不同的访问范围和服务对象,通常,每一个目录都会有一个相应的自定义类加载器去加载防止在里面的Java类库。
二.Tomcat可以使用默认的类加载机制吗?
答案是不行的。基于上面的四个问题来回答。
- 第一个问题,如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加载器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。
- 第二个问题,默认的类加载器是可以实现的,因为它的职责就是保证唯一性。
- 第三个问题和第一个问题一样,无法加载两个相同类库的不同版本。
- 第四个问题,要怎么实现jsp文件的HotSwap,jsp 文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。那么怎么办呢?我们可以直接卸载掉这jsp文件的类加载器,所以你应该想到了,每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器。重新创建类加载器,重新加载jsp文件。
三.Tomcat的类加载机制
前面3个类加载和默认的一致,CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader则是Tomcat自己定义的类加载器,它们分别加载/common/、/server/、/shared/*(在tomcat 6之后已经合并到根目录下的lib目录下)和/WebApp/WEB-INF/*中的Java类库。其中WebApp类加载器和Jsp类加载器通常会存在多个实例,每一个Web应用程序对应一个WebApp类加载器,每一个JSP文件对应一个Jsp类加载器。
- CommonClassLoader:Tomcat最基本的类加载器,加载路径下的类库可被Tomcat和所有应用程序共同使用。
- CatalinaClassLoader:Tomcat容器私有的类加载器,加载路径下的类库可被Tomcat使用,对所有的Web应用程序都不可见。
- SharedClassLoader:各个WebAPP共享的类加载器,加载路径下的类库可被所有的Web应用程序共同使用,但是对Tomcat不可见。
- WebappClassLoader:各个Webapp私有的类加载器,加载路径下的类库仅仅可被此Web应用程序使用,对Tomcat和其他的Web应用程序都不可见。
从图中的委派关系可以看出:CommonClassLoader能加载的类都可以被CatalinaClassLoader和SharedClassLoader使用,而CatalinaClassLoader和SharedClassLoader自己能加载的类与对方隔离。WebappClassLoader可以使用SharedClassLoader加载到的类,但各个WebappClassLoader实例之间相互隔离。而JsperLoader的加载范围仅仅是这个JSP文件所编译出来的哪一个Class,它出现的目的就是为了被丢弃:当服务器检测到JSP文件被修改时,会替换掉目前的JsperLoader实例,在通过建立一个新的Jsp类加载器来实现JSP的HotSwap功能。
Tomcat 违背了java 推荐的双亲委派模型了吗?答案是:违背了。双亲委派模型要求除了顶层的启动的类加载器之外,其他的类加载器都应当由自家的父类加载器加载。很显然,Tomcat不是这样实现的,为了实现隔离性,没有遵守这个约定。每个WebappClassLoader加载自己目录下的class文件,不会传递给父类加载器。
扩展提出一个问题:如果Tomcat的CommonClassLoader想加载WebappClassLoader中的类,要怎么办?
关于破坏双亲委派模型的内容,我们之前了解过,可以使用线程上下文加载器实现。使用线程上下文加载器getContextClassLoader,可以让父类加载器请求子类加载器去完成类加载的动作