深入理解Tomcat的Classpath

classpath就是一个参数,来告诉java虚拟机在哪里可以找到类和包去运行程序。classpath总是在程序源码外进行设置,将其同程序分开可以允许java代码以一种抽象的方式来引用类和包,允许程序可以在任何系统上被配置。为什么那些很有经验的java用户,他们已经非常清楚什么是classpath了,但是在Tomcat运行程序的时候,还是会遇到这样那样的问题呢?

一、为什么Classpaths给Tomcat用户带来了麻烦?

    classpath就是一个参数,来告诉java虚拟机在哪里可以找到类和包去运行程序。classpath总是在程序源码外进行设置,将其同程序分开可以允许java代码以一种抽象的方式来引用类和包,允许程序可以在任何系统上被配置。为什么那些很有经验的java用户,他们已经非常清楚什么是classpath了,但是在Tomcat运行程序的时候,还是会遇到这样那样的问题呢?

可能有以下三个原因:

    a、Tomcat处理classpaths的方式与其他java程序是不同的

    b、Tomcat的版本不同,其处理classpaths的方式也可能不同

    c、Tomcat的文档和默认的配置要求以一种特定的方式来完成某些事情。如果你不遵循这种方式,那么你就会陷入麻烦之中。

二、Tomcat的Classpath为什么不同于标准的classpath?

    Tomcat目的是尽可能的独立、直观和自动化,为了有效的管理,因此标准化web应用程序的配置和部署过程。同时,为了安全和命名空间的考虑,限制了对不同库的访问权限。这就是为什么不使用java中的classpath环境变量的原因了,java的classpath是声明依赖类库默认的地方,Tomcat的start脚本忽略了这个变量,在创建Tomcat系统Classloader的时候产生了自己的classpaths。

三、要理解Tomcat如何处理classpath的,看以下Tomcat6的启动过程。

    1、JVM的Bootstrap Loader(根类加载器)加载java的核心类库。java虚拟机使用JAVA_HOME环境变量来定位核心库的位置。

    2、startup.sh,使用start参数调用Catalina.sh,重写系统的classpath并加载bootstrap.jar和tomcat-juli.jar。这些资源仅对Tomcat可见。

    3、为每一个部署的Context创建ClassLoader,加载位于每个web应用程序WEB-INF/classes和WEB-INF/lib目录下的所有类和jar文件。每个web应用程序仅仅可见自己目录下的资源。

    4、Common ClassLoader加载位于$CATALINA_HOME/lib目录下的所有类和jar文件,这些资源对所有应用程序和Tomcat可见。

四、Tomcat6之前的Tomcat版本,类加载机制有那些不同:

a、Tomcat4.*

    Tomcat 4.x以及更早的版本,一个ServerLoader负责加载Catalina类,现在由CommonsLoader负责了。

b、Tomcat5.*

    Tomcat 5.x,一个shared loader负责加载在应用程序间共享的类,这些类位于$CATALINA_HOME/shared/lib。在Tomcat6中,这种方式被取消了。

    Tomcat 5.x也包括了一个Catalina loader,加载所有的Catalina组件,现在也被Common Loader取代了。

五、使用catalina.properties来配置Tomcat Classpath

    对于那些不想使用默认来加载方式的用户来说,幸运的是,Tomcat的classpath选项不是硬编码的,它们是从$CATALINA_HOME/conf/catalina.properties文件中读取的。

    这个文件包含了除bootstrap和system loader之外的所有其他的loaders,检查这个文件,你会有一些新发现:

    1、Server以及Shared loader还没有被删除,如果它们的属性没有定义,Commons loader负责处理。

    2、被各种loaders加载的类和jar文件不会被自动加载,它们只是用一个简单的通配符语法指定为一组。

    3、这里没有说你不能指定一个额外的仓库,事实上就是说你是可以的。

六、问题、解决方案和最佳实践

问题1:我的应用程序依赖一个外部的仓库,我不能引用它。

    让Tomcat知道一个外部的仓库,在catalina.properties文件的shared loader位置,使用正确的语法,声明这个仓库。语法基于你要配置的文件或仓库的类型:

    1、增加一个文件夹作为类仓库,使用“path/to/foldername”

    2、增加一个文件夹下的所有jar文件作为类仓库,使用"path/to/foldername/*.jar"

    3、增加单个jar文件作为类仓库,使用"file://path/to/foldername/jarname.jar"

    4、调用环境变量,使用${}格式,例如${VARIABLE_NAME}

    5、声明多个资源,用逗号分隔开

    6、所有的路径相对于CATALINA_BASE或CATALINA_HOME,或者是绝对路径

问题2:我想多个应用程序共享一个jar文件,这个jar文件在Tomcat里面。

    除了一些常见的第三方库(比如JDBC drivers),最好不要在$CATALINA_HOME/lib目录下包含额外的库,即使这样在一些情况下是可行的。应该重新创建比如/shared/lib和/shared/classes目录,然后在catalina.properties配置shared.loader属性:"shared/classes,shared/lib/*.jar"

问题3:我使用一个标准的应用程序,程序的war包含了依赖的所有包,但是我仍然遇到类定义错误。

这个问题可能是由许多事情引起的,包括编译或部署过程不是很正确,但是最有可能是web应用程序的目录结构不对造成的。java命名转换要求类名映射到存放这个类的目录结构。例如,一个类com.mycompany.mygreat.class需要被存放到目录WEB-INF/classes/com/mycompany/。经常代码中丢失一个点号就会引起classpath相关的问题。

问题4:web应用程序的不同模块需要使用同一个jar包的两个不同版本。

原 因:这种情况常常出现在一个应用程序中使用多个web框架,这些web框架依赖一个库的不同版本。

解决办法:

    有几种解决方案,但是它们都和classpath不相关。我们在这里说这个问题,是因为一些用户试图在框架的jar文件中的Manifest文件中指定依赖的库的不同版本的classpath,来解决这个问题。

    这种方式在一些web应用服务器中是支持的,所以一些用户想要在Tomcat中也使用这种方式。但是在Manifest文件中指定classpath在Tomcat中是不支持的,这也不是Servlet说明的一部分。

有以下四种方式来解决这个问题:

    1、你可以更新框架的版本,如果这里能够使得与其他框架依赖相同的版本。

    2、你可以创建两个或更多的自定义classloaders,每个jar文件一个,在WEB/INF/context.xml文件中配置,创建你所需要的两个不同版本的类的实例。

    3、你可以使用jar工具将框架和它依赖库打包成一个jar文件,那么它们会一起被加载。

    4、如果你发现你每隔一天就要处理这种情况,你应该考虑使用OSGI框架,这个框架有许多方法专门就是用来处理这种一个类的多个版本需要运行在同一个java虚拟机里的情况的。

最佳实践

    1、避免在Tomcat里使用Commons loader加载不属于Tomcat标准发布的库和包,这可能会引起兼容错误。如果你需要在多个应用程序间共享一个库或包,创建shared/lib和shared/classes目录,然后配置到catalina.properties文件的Shared loader。

    2、以上规则的一个例外就是常用的第三方共享库,例如JDBC driver。这些应该放到$CATALINA_HOME/lib目录下。

    3、尽可能按照Tomcat的开发者推荐的方式使用Tomcat,如果你发现你不得不频繁配置classpath,你可能需要重新考虑你的开发过程了。

一寸光阴一寸金,寸金难买寸光阴。——《增广贤文》
0 不喜欢
说说我的看法 -
全部评论(
没有评论
关于
本网站属于个人的非赢利性网站,转载的文章遵循原作者的版权声明,如果原文没有版权声明,请来信告知:hxstrive@outlook.com
公众号