ServletContext是Servlet与Servlet容器之间直接通信的接口。Servlet容器在启动一个Web应用时,会为它创建一个ServletContext对象。每个Web应用都有唯一的ServletContext对象,可以把Servlet对象形象的理解为Web应用的总管家,同时一个Web应用中的所有Servlet对象都共享一个ServletContext,所有的Servlet对象都可以通过ServletContext来访问容器中的各种资源。当Servlet容器终止一个Web应用时,就会销毁它的ServletContext对象,由此可见,ServletContext对象与Web应用具有同样的生命周期。

在ServletContext对象中提供了一组在Web应用范围内存取共享数据的方法。"范围"在这里有两层含义:

●  表示一个特定时间段。

●  表示在特定时间段内可以共享数据的所有Web组件的集合。

Web应用范围具有以下两层含义:

●  表示由Web应用的生命周期构成的时间段。

●  示在Web应用的生命周期内所有Web组件的集合。

存放在Web应用范围内的共享数据具有以下特点:

●  共享数据的生命周期位于Web应用的生命周期中的一个时间片段内。

●  共享数据可以被Web应用中的所有Web组件共享。

如何实现向Web应用范围内存取共享数据呢?由于ServletContext对象具有Web应用同样常的生命周期,而且ServletContext对象可以被Web应用中的所有Web组件共享,因此可以利用ServletContext对象来存取Web应用范围内的共享数据,基本思想如下:

面向对象编程的一个基本思想就是万物皆对象,因此,共享数据也理所当然的用java.lang.Object类型的任意Java对象来表示。

只要把代表共享数据的Java对象与ServletContext对象关联,该Java对象的生命周期就依附于ServletContext对象的生命周期,并且Web组件可以通过ServletContext对象来访问它。实际上,该Java对象就被存放到Web应用范围内。

在Web应用范围内可以存放各种类型的共享数据。为了方便地存取特定的共享数据。可以把代表共享数据的Java对象作为ServletContext的属性来存放,每个属性包括一对属性名和属性值,属性名用来识别共享数据,属性值则代表共享的数据。

在ServletContext对象中常用的用于存取共享数据的方法包括以下几种:

●  setAttribute(String name,Object object):向Web应用范围内存入共享数据,参数name指定属性名,参数object表示共享数据。

●  getAttribute(String name):根据参数给定的属性名,返回Web应用范围内匹配的共享数据。

●  removeAttribute(String name):根据参数给定的属性名,从Web应用范围内删除匹配的共享数据。

接下来介绍一个向Web应用范围内存放共享数据的范例。许多网站都能统计特定网页被客户端访问的次数,JavaWeb应用就可以利用ServletContext来实现这一功能。

首先,设计一个用于累计访问次数的计数器,用Counter类来实现。Counter类是一个普通的基于JavaBean风格的Java类,具体信息如下:

public class Countter{

    private int count; //计数值

    public Counter(){

         this(0);

    }

    public Counter(int count){

         this.count = count;

    }

    public void setCount(int count){

         this.count = count;

    }

    public int getCount(){

         return count;

    }

    public void add(int step){

         count+=step;

    }

}

接下来把计数器存放在Web应用范围内,每当网页被客户端请求访问一次,计数器就会递增一次,下面的CounterServlet类实现了向应用范围内存取计数器的功能。

public class CounterServet extends HttpServlet{

    public void service(HttpServletRequest request,

           HttpServletResponse response)throws ServletException,IOException{

         ServletContext sc = this.getServletContext();

         Counter counter = (Counter)sc.getAttribute("counter");

         if(counter==null){

              counter = new Counter(1);

              sc.setAttribute("counter",counter);

         }

         response.setContentType("text/html;charset=utf-8");

         PrintWriter out = response.getWriter();

         out.println("欢迎光临本网站。您是第" + counter.getCount() + "位访问者");

         counter.add(1);

         out.close();

    }

}

以上代码在ServletContext中设置了一个Counter类型的属性。属性名为counter,CounterServlet的service()方法从ServletContext中读取counter属性。如果counter属性不存在,那么就创建一个Counter对象,把它作为counter属性存放在ServletContext中,即把代表计数器的Counter对象存放在Web应用范围内。service()方法接下来向客户端输出当前Counter对象的计数值,然后将Counter对象的计数值加1。

下面的CounterClearServlet类负责将Web应用范围内的计数器删除,这个Servlet类没有什么实际的意义,仅仅为了进一步帮助大家理解ServletContext的作用。

public class CounterClearServlet{

    public void service(HttpServletRequest request,

            HttpServletResponse response)throws ServletException,IOException{

        ServletContext sc = this.getServletContext();

        sc.removeAttribute("counter");

        PrintWriter out = response.getWriter();

        out.println("The counter us removed.");

        out.close();

    }

}

在web.xml文件中为CounterServlet和CounterClearServlet映射的URL分别为"/counter"和"/clear"。接下来按下面步骤运行范例。

1.启动Tomcat,在浏览器中通过http://localhost:8080/helloapp/counter访问CounterServlet。在第一次访问该servlet时,浏览器端显示计数器的值为1。

2.刷新上述访问CounterServlet的页面,会看到每刷新一次,计数器的值增加1,假定最后一次刷新后的计数器的值为5.

3.另外在打开一个新的浏览器,也访问CounterServlet,此时计数器的值为6。

4.在浏览器中通过http://localhost:8080/helloapp/clear访问CounterClearServlet,然后在访问CounterServlet,浏览器端显示计数器的值为1。

5.多次刷新上述访问CounterServlet的页面,会看到每刷新一次,计数器的值增加1,假定最后一次刷新后计数器的值为5.

6.停止Tomcat服务器,在重新启动Tomcat服务器,然后在通过浏览器访问CounterServlet,计数器的值又被初始化为1。

7.赋值helloapp应用到Tomcat服务器中并改名为hellpapp2,启动Tomcat服务器,此时在服务器中已经有了两个应用,helloapp和helloapp2,在两个浏览器中分别多次通过以下URL访问helloapp应用以及helloapp2应用中的CounterServlet:

http://localhost:8080/helloapp/counter

http://localhost:8080/helloapp2/counter

两个浏览器中的计数器的数值将各自独立地递增,由此可见,这两个Web应用拥有各自独立的计数器。

通过上述实验可以看出,在ServletContext中设置的属性,在Web应用运行期间一直存在,除非通过ServletContext的removeAttribute()方法将其删除。当Web应用被终止时,Servlet容器会销毁ServletContext对象,存储在ServletContext对象中的属性自然也不复存在,不用Web应用的ServletContext对象各自独立。图2.7演示了Servlet容器运行时Web应用中的各个Java对象之间的关系。

7a704d03d1244f179d2d3f6f09e1443b.png

Servlet容器运行时Web应用中的各个Java对象之间的关系


从上图可以看出,在helloapp应用中,CounterServlet和CounterClearServlet对象共享一个ServletContext对象,因此也共享与ServletContext对象关联的Conter对象,这种对象之间的关系同样适用于helloapp2应用。


使用ServletContextListener监听器

在Servlet API中有一个ServletContextListeren接口,它能够监听ServletContext对象的生命周期,实际上就是监听Web应用的生命周期。

当Servlet容器启动或终止Web应用时,会触发ServletContextEvent事件,该事件由ServletContextListener来处理。在ServletContextListeren接口中定义了处理ServletContextEvent事件的两个方法。

contextInitialized(ServletContextEvent sce):当Servlet容器启动Web应用时调用该方法。在调用完该方法之后,容器在对Filter初始化,并且对那些在Web应用启动时就需要被初始化的Servlet进行初始化。

contextDestroyed(ServletContextEvent sce):当Servlet容器终止Web应用时调用该方法。在调用该方法之前,容器会先销毁所有的Servlet和Filter过滤器。

下面通过一个例子来介绍ServletContextListener的用法。前面案例中的CounterServlet类只能统计当Web应用启动后,网页被客户端访问的次数。如果重启Web应用,计数器又会重新从1开始统计访问次数。在实际应用中,往往需要统计自Web应用被发布后网页被客户端访问的次数,这就要求当Web应用被终止时,计数器的数值被永远存储在一个文件中或者数据库中,等到Web应用重新启动时,先从文件或数据库中读取计数器的初始值,然后在此技术上继续计数。

向文件中写入或读取计数器的数值的功能可以由自定义的MyServletContextListener类来完成,它具有以下功能:

在Web应用启动时从文件中读取计数器的数值,并把表示计数器的Counter对象存放为Web应用范围内,存放计数器的文件的路径为helloapp/count/count.txt

在Web应用终止时把Web应用范围内的计数器的数值保存到counter.txt文件中。

public class MyServletContextLinstener extends ServletContextListener{

    public void contextInitialized(ServletContextEvent sce){

         System.out.println("hellpapp application is Initialized");

         ServletContext sc = sce.getServletContext();

         try{

              BufferedReader reader = new BufferedReader

                         (new InputStreamReader (sc.getResourceAsStream

                         ("/counte/count.txt")));

              int count = Integer.parseInt(reader.readLine());

              reader.close();

              Counter counter = new Counter(count);

              sc.setAttribute("counter",counter);

         }catch(IOException e){

              e.printStackTrace();

         }

    }

    public void contextDestroyed(ServletContextEvent sce){

         System.out.println("helloapp application is Destroyed.");

         ServletContext sc = sce.getServletContext();

         Counter counter = (Counter)sc.getAttribute("counter");

         if(counter!=null){

              try{

                   String filepath = sc.getRealPath("/count");

                   filepath = filepath +"/count.txt";

                   PrintWriter pw = new PrintWriter(filepath);

                   pw.println(counter.getCount());

                   pw.close();

              }catch(IOException e){

                   e.printStackTrace();

              }

         }

    }

}

用户自定义的MyServletContextListener监听器只有先向Servlet容器注册,Servlet容器在启动或终止Web应用时,才会调用该监视器的相关方法,在web.xml文件中,<listener>元素用于向容器中注册监听器:

<listener>

<listener-class>com.xdl.MyServletCountextListener</listener-class>

</listener>

下面按如下步骤演示MyServletContextListener监听器的作用。

1.在helloapp/count目录下创建count.txt文件,在该文件中存放了一个数字"5",

2.启动Tomcat,在浏览器中通过http://localhost:8080/helloapp/counter访问CounterServlet,在第一次访问该Servlet时,浏览器端显示计数器的值为5。

3.刷新上述访问CounterServlet的页面,会看到每刷新一次,计数器的值增加1,假定最后一次刷新后的计数器的值为10。

4.停止Tomcat服务器,查看helloapp/count/count.txt文件,会发现在该文件中存放的数字变为10.

5.启动Tomcat服务器,在浏览器中再次访问CounterServlet,当第一次访问该Servlet时,浏览器端显示计数器的值为10。

从上面的案例中可以看出,MyServletContextListener监听器与CounterServlet共享Web应用范围内的代表计数器的Counter对象。监听器在Web应用启动或终止时会操纵Counter对象,而Servlet在每次响应客户请求时会操纵Counter对象。