背景

一个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)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
       Bootstrap
           |
       Extension
           |
      Application
           |
        System
           |
         Common
        /      \
   Catalina    Shared
              /      \
        WebApp1 ... WebApp2
           |           |
         Jasper      Jasper

解决方案

在另外两个应用中添加xerces:xercesImpl:jar:2.6.2依赖。