一个Tomcat类加载问题
背景
一个Tomcat实例中运行了三个应用,其中一个对接了Apereo的CAS系统。现在要求另外两个系统也对接CAS系统,问题就出现了:
应用启动后打开其中两个应用的任何一个,登录完成后系统都没有问题。唯独首选打开第三个,其他两个报错ClassNotFoundException: org.apache.xerces.parsers.SAXParser。
发现这个类来自xerces:xercesImpl:jar:2.6.2
,使用mvn dependency:tree
发现是被xom:xom:1.1简洁引用。
分析
CAS client jar中使用XMLReaderFactory创建XMLReader,首次创建会从classpath中查找META-INF/services/org.xml.sax.driver
文件,这个文件里的内容是一个类的全名。比如xercesImpl中该文件的内容是org.apache.xerces.parsers.SAXParser
。
找到之后会将类名保存在XMLReaderFactory的静态变量_clsFromJar,并标记不会再查找org.xml.sax.driver文件。找不到的话则使用com.sun.org.apache.xerces.internal.parsers.SAXParser
类。
然后再使用当前线程的ContextClassLoader对类进行加载,这里的的ContextClassLoader是一个WebAppClassLoader的实例。
同时XMLReaderFactory类是被BootStrapClassLoader加载的,为三个应用共享。
Tomcat类记载机制
Tomcat中有四个位置可以存放Java类库:/commons、/server、/shared和各Web应用的WEB-INF/lib目录。
/commons目录中的类库可以被Tomcat和所有Web应用使用 /server目录中的类库只能被Tomcat使用 /shared目录中的可以被所有Web应用的使用,但是对Tomcat不可见 各Web应用的WEB-INF/lib目录中的类库则只能被该的应用使用
Tomcat的使用CommonClassLoader、CatalinaClassLoader、SharedClassLoader、WebAPPClassLoader加载对应目录中的类库。
Bootstrap、Extension、Application是虚拟机使用的系统类加载器。
类的加载使用双亲委派机制(Parent-Delegation)。
Bootstrap
|
Extension
|
Application
|
System
|
Common
/ \
Catalina Shared
/ \
WebApp1 ... WebApp2
| |
Jasper Jasper
解决方案
在另外两个应用中添加xerces:xercesImpl:jar:2.6.2
依赖。