Singleton 模式通过私有化构造方法和 static 成员变量,提供全局范围内的唯一实例。然而,在 WEB 应用程序中,存在一个陷阱!

1、编写 Singleton 类

先来看一个单例: TestStaticInWeb.java


Code:

[Ctrl+A Select All]


这个单例很简单,通过 getInstance() 获得唯一实例,通过 getCreateTime() 来获得该唯一实例的创建时间。

普通情况下,这是没问题的,然而,在 Web 应用程序中,它的表现又如何呢?

2、构建 Web 应用程序

(1)编写一个 JSP,在这个 JSP 中显示出上述单例的相关信息:index.jsp


Code:

[Ctrl+A Select All]


(2)创建一个目录,名称为 testA,在 testA 下建 WEB-INF

(3)在 WEB-INF 下建 lib 目录

(4)在 WEB-INF 目录下建立 web.xml


Code:

[Ctrl+A Select All]


(5)把 index.jsp 放到 testA 目录下

(6)把 TestStaticInWeb.java 编译并打包成 test.jar,并拷贝到 testA\WEB-INF\lib 目录下

(7)把 testA 目录整个复制一份,然后重命名为 testB

(8)把 testA 和 testB 一起拷贝到 tomcat\webapps 目录下去

(9)启动 Tomcat
 
3、查看运行结果

(1)打开 IE,输入 [url]http://localhost:8080/testA[/url],得下如下结果:
testA-1



(2)输入 [url]http://localhost:8080/testB[/url],得到如下结果:
testB-1



显然,上述两个结果是不一样的!在 Tomcat,显然存在两个 TestStaticInWeb 类的实例,也就是说,通过单例模式,得到的唯一实例并不“唯一”!

4、原因何在

在一个 ClassLoader 的作用范围内,一个类只会被 load 一次,对于用 static 修改的类或变量,也只会存在一份。

通常情况下,一个 JVM 对应着一个 ClassLoader,但在 Tomcat 等 J2EE 容器内,ClassLoader 则是相对复杂的,一个 JVM 可能对应着多个 ClassLoader,而且 ClassLoader 之间,是层次的结构。根据 Web 应用程序的相关规范(参见 Java Servlet SpecificationVersion 2.4 第 9 章:Web Applications),部署在 Tomcat 中的每个 webapp 都有一个与之对应的特殊的 ClassLoader,这个 ClassLoader 是在系统的 System ClassLoader 的基础上,优先 load 位于 WEB-INF/classes 和 WEB-INF/lib/*.jar 中的 class 文件。

由于 testA 和 testB 分属于两个不同的 webapp,所以,它们所得到的 TestStaticInWeb 类的 ClassLoader 也不一样。此时,单例模式只能保证在同一个 webapp 内部唯一,不能保证一个 JVM(即 Tomcat 容器)范围内唯一。

5、解决办法

如果确实要在 Tomcat 范围内唯一,则可以把 test.jar 拷贝到 Tomcat\common\lib 目录下去,并从 WEB-INF\lib 目录下删除之(重要!否则会出错!)。

以下是拷贝到 Tomcat\common\lib 目录下去之后的结果:

可以看出,这种方案下,TestStaticInWeb 类都是由系统的 ClassLoader 读取和初始化的,能保证全局唯一。
testA-2


testB-2



6、下载

单击这里可以下载 WEB 应用程序,源码位于 WEB-INF/src 目录下。

本文中的例子,在 Tomcat 5.5.15 + JDK 1.4 环境下通过。