陷阱:在 WebApp 中谨防 Singleton 错误

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

1、编写 Singleton 类

先来看一个单例: TestStaticInWeb.java


Code:
package test; public class TestStaticInWeb {       private static TestStaticInWeb test = null;             private long createTime = 0;             public static TestStaticInWeb getInstance() {           if (test == null)               test = new TestStaticInWeb();           return test;       }       private TestStaticInWeb() {           createTime = System.currentTimeMillis();       }             public long getCreateTime() {           return this.createTime;       } }
[Ctrl+A Select All]


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

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

2、构建 Web 应用程序

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


Code:
<%@ page language="java" contentType="text/html; charset=GB18030" pageEncoding="GB18030" %> <%@ page import="test.*" %> <HTML> <HEAD> <TITLE>TestStaticInWeb in <%= request.getContextPath().substring(1) %></TITLE> </HEAD> <BODY> <P>访问路径是:<%= request.getRequestURI() %></P> <P>单例对象是:<%= TestStaticInWeb.getInstance() %><p> <P>创建时间是:<%= TestStaticInWeb.getInstance().getCreateTime() %></P> <P>ClassLoader是:<%= TestStaticInWeb.getInstance().getClass().getClassLoader() %></P> </BODY> </HTML>
[Ctrl+A Select All]


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

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

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


Code:
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app   PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"   "[url]http://java.sun.com/j2ee/dtds/web-app_2_2.dtd[/url]"> <web-app>   <display-name>TestStaticInWeb</display-name>   <welcome-file-list>     <welcome-file>index.jsp</welcome-file>   </welcome-file-list> </web-app>
[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 环境下通过。

你可能感兴趣的:(java,Singleton,Web,webapp,休闲)