tomcat classloader 加载class顺序

今天下午被同事问到如果我有两个不同版本的class分别放在两个module里如何处理(我们项目里的module是分布在不同的war包里的)

然后引出下面的问题

1. 完全相同(同包同名)的class如何加载

2. 完全相同的class如果在不同war包里如何加载

印象中应该是不同war包各自加载自己的, 猜测是因为会启动各自的application级别classloader, 稍微调查之后发现自己原来的认识不够深入

参考 http://tomcat.apache.org/tomcat-7.0-doc/class-loader-howto.html

我们知道jvm加载class默认是采用 父类委托 模型, 这个模型在之前的文章中我提到过

参考 http://lizhe.name.csdn.net/node/91

java默认提供3种classloader

1. Bootstrp loader 

加载%JAVA_HOME%/jre/lib,-Xbootclasspath参数指定的路径以及%JAVA_HOME%/jre/classes中的类

2. ExtClassLoader  

加载%JAVA_HOME%/jre/lib/ext,此路径下的所有classes目录以及java.ext.dirs系统变量指定的路径中类库

3. AppClassLoader

加载classpath所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器

getSystemClassLoader 返回的就是这个加载器

当一个classloader需要去加载类com.test.A时, 它首先会在自己的加载缓存中查找这个类,如果发现就直接返回,如果没有发现并不会直接加载,而是让自己的父类加载器去加载

等一下, 父类加载器会立即加载这个类么, 当然不是, 父类加载器也会采用同样的策略, 在自己的缓存中查找,如果没有找到, 就委托给自己的父类, 一直到Bootstrp loader 

如果一直到Bootstrp loader 这一层仍然找不到这个类, classloader才会自己加载它

tomcat实际上采用了不同的类加载机制

这里tomcat的Bootstrap实际上包含了jvm的Bootstrap和ExtClassLoader ($JAVA_HOME/jre/lib/ext), 加载了一些jvm最基本的类

System classloader 忽略系统本身的classpath, 加载

$CATALINA_HOME/bin/bootstrap.jar — Contains the main() method that is used to initialize the Tomcat server, and the class loader implementation classes it depends on.

$CATALINA_BASE/bin/tomcat-juli.jar or $CATALINA_HOME/bin/tomcat-juli.jar — Logging implementation classes. These include enhancement classes to java.util.logging API, known as Tomcat JULI, and a package-renamed copy of Apache Commons Logging library used internally by Tomcat. See logging documentation for more details.

If tomcat-juli.jar is present in $CATALINA_BASE/bin, it is used instead of the one in $CATALINA_HOME/bin. It is useful in certain logging configurations

$CATALINA_HOME/bin/commons-daemon.jar — The classes from Apache Commons Daemon project. This JAR file is not present in the CLASSPATH built by catalina.bat|.sh scripts, but is referenced from the manifest file of bootstrap.jar.

Common classloader 用于加载tomcat lib目录下的jar和class, 不要把webapp相关jar放在这里

unpacked classes and resources in $CATALINA_BASE/lib
JAR files in $CATALINA_BASE/lib
unpacked classes and resources in $CATALINA_HOME/lib
JAR files in $CATALINA_HOME/lib
  • annotations-api.jar — JavaEE annotations classes.
  • catalina.jar — Implementation of the Catalina servlet container portion of Tomcat.
  • catalina-ant.jar — Tomcat Catalina Ant tasks.
  • catalina-ha.jar — High availability package.
  • catalina-tribes.jar — Group communication package.
  • ecj-*.jar — Eclipse JDT Java compiler.
  • el-api.jar — EL 2.2 API.
  • jasper.jar — Tomcat Jasper JSP Compiler and Runtime.
  • jasper-el.jar — Tomcat Jasper EL implementation.
  • jsp-api.jar — JSP 2.2 API.
  • servlet-api.jar — Servlet 3.0 API.
  • tomcat-api.jar — Several interfaces defined by Tomcat.
  • tomcat-coyote.jar — Tomcat connectors and utility classes.
  • tomcat-dbcp.jar — Database connection pool implementation based on package-renamed copy of Apache Commons Pool and Apache Commons DBCP.
  • tomcat-i18n-**.jar — Optional JARs containing resource bundles for other languages. As default bundles are also included in each individual JAR, they can be safely removed if no internationalization of messages is needed.
  • tomcat-jdbc.jar — An alternative database connection pool implementation, known as Tomcat JDBC pool. See documentation for more details.
  • tomcat-util.jar — Common classes used by various components of Apache Tomcat.
  • tomcat7-websocket.jar — WebSocket 1.1 implementation
  • websocket-api.jar — WebSocket 1.1 API

下面重点来了

WebappX  加载器闪亮登场

WebappX — A class loader is created for each web application that is deployed in a single Tomcat instance. All unpacked classes and resources in the /WEB-INF/classes directory of your web application, plus classes and resources in JAR files under the /WEB-INF/lib directory of your web application, are made visible to this web application, but not to other ones.

也就是说, 每个webapp都会拥有一个独立的WebappX加载器, 它会加载/WEB-INF/classes和/WEB-INF/lib下的所有资源, 只对这个webapp可见,不共享给其他webapp

这个行为是基于 Servlet Specification, version 2.4, section 9.7.2 Web Application Classloader 有兴趣的朋友可以去读一下

WebappX  会先尝试自己加载需要的类,而不是使用父类委托模型委托给自己的父加载器, 但是这里有一些例外, JRE base classes不会被这个加载器处理,还有就是Servlet API classes也不会被这个加载器加载, 所以不要把这些例外放在自己的WEB-INF/lib文件夹里

默认的加载顺序是

  • Bootstrap classes of your JVM
  • /WEB-INF/classes of your web application
  • /WEB-INF/lib/*.jar of your web application
  • System class loader classes (described above)
  • Common class loader classes (described above)

如果设置了<Loader delegate=”true”/> 则加载顺序是

  • Bootstrap classes of your JVM
  • System class loader classes (described above)
  • Common class loader classes (described above)
  • /WEB-INF/classes of your web application
  • /WEB-INF/lib/*.jar of your web application

最后我要加入一个自己以往的经验

测试的时候如果需要在tomcat中放入同包同名的类(有时候为了测试方便,比如com.lizhe.Test在一个已经被部署的jar包里,但是测试的时候为了覆盖掉)

可以直接在classes文件夹里放入com.lizhe.Test.class 这个class会被优先加载

如果需要覆盖一个jar包, 比如test.jar,可以将新的jar包命名为aaaaa.jar , 因为根据实际测试, tomcat加载同路径下的jar包时会按照字母顺序排序,aaaaa.jar会优先于test.jar加载