第 14 章 生命周期

注意

讲一下servlet的生命周期与运行时的线程模型,对了解servlet的运行原理有所帮助,这样才能避免一些有冲突的设计。

如果你不满足以下任一条件,请继续阅读,否则请跳过此后的部分,进入下一章:第 15 章 分页

  1. 了解servlet的生命周期。

  2. 了解servlet运行时的线程模型,及设计程序时需要注意的部分。

14.1. 生命周期

我们之前使用的都是javax.servlet.http.HttpServlet,这个类实现了javax.servlet.Servlet接口,而这个接口中定义的三个方法是所有servlet都必须实现的。

package javax.servlet;

public interface Servlet {

    void init(ServletConfig config);

    void service(ServletRequest request, ServletResponse response);

    void destroy();

}
        

如图所示,tomcat之类的服务器首先根据web.xml中的定义实例化servlet,然后调用它的init()方法进行初始化,init()方法的ServletConfig参数是服务器传递进servlet的,其中包含web.xml配置的初始化信息和ServletContext对象等共享内容。

初始化后的servlet实例便进入等待请求的状态,当有与servlet-mapping匹配的请求进入时,服务器会调用servlet实例的service方法,传入ServletRequest与ServletResponse两个参数等待servlet处理完毕。

注意一点,对于每个web应用,内存中只存在一个servlet实例,所有请求都是调用这个servlet实例,所以我们说servlet不是线程安全的,所有操作都要限制在service()方法中进行,不要在servlet中定义类变量。(doGet()和doPost()是HttpServlet覆盖service()方法后分支出来的辅助方法,实际上服务器调用的还是service()。)

当web应用卸载时,服务器会调用每个已经初始化的servlet的destroy(),然后销毁这些servlet实例,如果你需要在servlet销毁时释放什么资源的话,可以写在destory()方法中。

那么servlet是在什么时候进行初始化的呢?我们可以通过web.xml中的load-on-startup标签。

<servlet>
<servlet-name>TestServlet</servlet-name>
<servlet-class>anni.TestServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

load-on-startup的值是一个整数,当它大于等于零的时候服务器会在web发布的时候初始化servlet。当它小于零或者我们没有设置load-on-startup的时候,服务器会在用户第一次访问servlet的时候才去初始化servlet。

或许你对load-on-startup为什么是一个整数存有疑问,为什么不是true和false呢?这是因为如果我们在web.xml中设置了多个servlet的时候,可以使用load-on-startup来指定servlet的加载顺序,服务器会根据load-on-startup的大小依次对servlet进行初始化。不过即使我们将load-on-startup设置重复也不会出现异常,服务器会自己决定初始化顺序。

回头看看javax.servlet.Filter中也有init()和destroy()方法,它的声明周期与servlet基本一致,服务器使用init()对Filter初始化,销毁Filter的时候调用destroy()方法,只是过滤器就不在有load-on-startup设置了,它总是会在服务器启动的时候进行初始化,然后按照web.xml定义的顺序依次执行。

14.2. 线程模型

我们做一个试验,以此来证明某些编写servlet的方法是绝对错误的。

第一步,我们打开浏览器,浏览14-02的index.jsp页面,输入“叮咚”。

第二步,我们再打开一个14-02/index.jsp页面,输入“lingirl”。

第三步,点击第一个页面的提交按钮,然后在10秒之内点击另一个页面的提交按钮,等两个页面都提交成功后,我们会看到如下页面。

url上有乱码这个就是提交“叮咚”的页面,会惊讶吧?本来这时应该显示“叮咚”的。

这个页面对应提交“lingirl”的页面,它似乎是显示正常的。

到底是哪里出错了,为什么第一个页面提交了数据,却得到第二个页面提交的结果,首先让我们看一下TestServlet的代码。

package anni;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; public class TestServlet extends HttpServlet { private String username; public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
this.username = request.getParameter("username"); try {
Thread.sleep(10000);
} catch(InterruptedException ex) {
} response.getWriter().write(this.username);
} }

doGet()方法中从request中获得username参数,然后赋给this.username,这是一个类变量。然后暂停10秒,这10秒我们假设正在进行一些很费时间的计算,这样我们就有十秒钟去点两个页面的提交按钮了。最后将this.username写入response。

你也许在想:“这没有问题啊,第一个页面提交了数据,等待10秒返回,第二个页面再提交数据,等待10秒返回,两者并不冲突啊。”可实际上在多线程模型中不会有这种队列让请求一个一个执行,所有请求都是蜂拥而至。

在这个例子里,第一个请求过来将“叮咚”赋值给this.username后进行等待,10秒之内我们的第二个请求又调用了doGet()方法,并把this.username修改为“lingirl”,等到10秒后第一个请求结束等待后,获得的this.username已经是“lingirl”了。

this.username这种写法在servlet中是绝对禁用的,如果有什么信息需要保存,可以考虑放到session或ServletContext中。

14.3. 在jsp中定义类变量

写在<%%>之间的代码,在转换成servlet之后都会service()方法内运行,所以我们不必担心出现上边this.username的问题。

但是我们可以用<%!%>(注意多出来的感叹号)定义类变量或类方法,把上一个罪大恶极的servlet改造成jsp的话,就像这样。

<%@ page contentType="text/html; charset=gb2312"%>
<%!
String username;
%>
<%
this.username = request.getParameter("username");
try {
Thread.sleep(10000);
} catch(InterruptedException ex) {
}
out.write(this.username);
%>

注意

使用14-03下的例子可以测试jsp出错的效果,记得要在10秒之内点击两次。

<%!%>似乎是一个巨大的陷阱,如果我们使用它定义类变量就一定会出现多线程错误。

不过凡事都有正反两面,当我们需要在jsp中定义一个通用方法时,就需要借助<%!%>的力量了,假设我们需要一个方法,根据用户的性别显示不同的html内容,如果sex = 0就输出红色的“男”,如果sex = 1就输出绿色的“女”。为实现这个功能,我们可以定义一个sexRenderer()方法。

14-04/index.jsp页面显示效果如下:

index.jsp中的代码分两部分。

第一部分定义sexRenderer()方法和

<%!
public String sexRenderer(int sex) {
if (sex == 0) {
return "<span style='color:red;'>男</span>";
} else if (sex == 1) {
return "<span style='color:green;'>女</span>";
} else {
return "";
}
}
%>

第二部分循环显示保存了性别信息的数组,显示的时候将会调用sexRenderer()方法。

<%
int[] people = {0, 1, 1, 0};
for (int i = 0; i < this.people.length; i++) {
%>
<tr>
<td><%=this.sexRenderer(this.people[i])%></td>
</tr>
<%
}
%>

好的,现在我们知道可以在<%!%>中定义方法和变量了。但是同时也要了解的是<%!%>已经脱离了service()方法,这就导致不能在它里边使用request,response这些默认变量了,如果想要调用request只能写成void doSomething(HttpServletRequest request)的形式了,稍微注意一下即可。

14.4. jsp九大默认对象

分别是request, response, out, pageContext, session, application, page, config, exception。

让我们看看它们与servlet中变量的对应关系。

首先要明确的是,这九个变量都只在<%%>中有效,<%!%>中是无法调用这九个对象的。实际上<%%>最后会成为service()方法中的代码,我们这里就看看如何在service()方法中获得这些对象吧。

  1. request

    public void service(ServletRequest req, ServletResponse res) {
    HttpServletRequest request = (HttpServletRequest) req;
    }

    jsp中的request就是service()中传入的req参数,因为service中定义的是ServletRequest类型,我们还需要转换成HttpServletRequest类型。

  2. response

    public void service(ServletRequest req, ServletResponse res) {
    HttpServletResponse response = (HttpServletResponse) res;
    }

    与上例相同,response也是service()中传入的res参数。

  3. out

    Writer out = response.getWriter();
                    

    out对应着从response中取出的writer对象,负责向响应中输出数据。不过jsp和servlet中的out还是有一点区别,虽然它们都实现了java.io.Writer接口,但servlet中实际类型是java.io.PrintWriter,而jsp中实际类型是javax.servlet.jsp.JspWriter。

  4. pageContext

    这是jsp独有的,servlet里没有page的概念。

  5. session

    HttpSession session = request.getSession();
                    

    直接从request中获得会话。

  6. application

    ServletConext application = getServletConfig().getServletContext();
                    

    可以通过servletConfig获得ServletContext,这是整个web应用共享的一个对象。

  7. page

    Object page = this;
                    

    page就代表当前jsp对象,也可以直接使用this引用。

  8. config

    ServletConfig config = getServletConfig();
                    

    这是在servlet初始化时由服务器传入的对象,可以通过它获得web.xml中定义的初始化参数。

  9. exception

    想在jsp中使用这个对象需要满足一些条件了。

    首先我们要在14-05/index.jsp中故意抛出一个异常。

    <%@ page contentType="text/html; charset=gb2312" errorPage="error.jsp"%>
    <%
    String str = null;
    str.length();
    %>

    str值是null,直接在null上调用length()方法会引发NullPointerException,然后我们可以看到页面第一行使用jsp指令(directive)设置了errorPage="error.jsp",这样在出现异常的时候就会自动forward到error.jsp中。现在看看error.jsp中有些什么。

    <%@ page contentType="text/html; charset=gb2312" isErrorPage="true"%>
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
    <title>index</title>
    </head>
    <body>
    <%=exception%>
    </body>
    </html>

    最主要的是在jsp指令(directive)中设置isErrorPage="true",这样我们就可以在jsp中使用exception对象了,实际上这个异常是从request中取出来的。

到此为止,jsp九大默认对象已经讲解完毕,其中常用的还是四个作用域对应的对象,其他的了解即可。

servlet的生命周期与运行时的线程模型的更多相关文章

  1. Servlet的生命周期与运行原理

    Servlet的生命周期:    1 加载classLoader    2 实例化 new    3 初始化 init(ServletConfig)    4 处理请求 service doGet d ...

  2. (转)Servlet的生命周期——初始化、运行、销毁全部过程

    背景:面试中很基础的一个问题,所以有必要好好整理一番. Servlet体系结构是建立在 Java 多线程机制上的,它的生命周期由 Web 容器负责. 当客户端第一次请求某个 Servlet 时,Ser ...

  3. Servlet的生命周期及工作原理

    Servlet生命周期分为三个阶段: 1,初始化阶段  调用init()方法 2,响应客户请求阶段 调用service()方法 3,终止阶段 调用destroy()方法 Servlet初始化阶段: 在 ...

  4. Servlet基础(二) Servlet的生命周期

    Servlet基础(二) Servlet的生命周期 Servlet的生命周期可以分为三个阶段: 1.初始化阶段 2.响应客户请求阶段 3.终止阶段 Servlet的初始化阶段 在下列时刻Servlet ...

  5. JSP Servlet WEB生命周期

    [转载] JavaWeb的生命周期是由Servlet容器来控制的总的来说分为三个阶段1.启动阶段:加载web应用相关数据,创建ServletContext对象,对Filter和servlet进行初始化 ...

  6. Servlet/JSP-01 Servlet及其生命周期

    一.起步 1.新建一个类继承Servlet接口 public class HelloServlet implements Servlet { @Override public void destroy ...

  7. JavaWeb学习之Servlet(二)----Servlet的生命周期、继承结构、修改Servlet模板

    [声明] 欢迎转载,但请保留文章原始出处→_→ 文章来源:http://www.cnblogs.com/smyhvae/p/4140466.html 一.http协议回顾: 在上一篇文章中:JavaW ...

  8. Servlet学习笔记(1)--第一个servlet&&三种状态对象(cookie,session,application)&&Servlet的生命周期

    servlet的404错误困扰了两天,各种方法都试过了,翻书逛论坛终于把问题解决了,写此博客来纪念自己的第一个servlet经历. 下面我会将自己的编写第一个servlet的详细过程提供给初学者,大神 ...

  9. servlet的生命周期与工作原理、使用!

    概念: Servlet是一个java程序运行在服务器上,处理客户端请求并做粗响应的程序!Servlet是和平台无关的服务器组件,它运行在Servlet容器中,Servlet容器 负责servlet和客 ...

随机推荐

  1. loadrunner参数化excel数据

    LR参数化数据源Oracle,MSSQL,Excel参数化的方法: 重点介绍excel数据参数化的方法: 1.首先创建excel表格: 注意要写列明   2.创建excel表连接:   参数化完成后, ...

  2. WebApi多数据库切换

    用抽象工厂来解决多数据库之间的切换问题是普遍的,像以下几篇文章都讲的很具体 申明之前写的存在强大漏洞 -- 之前有涉及到IoC Autofac的知识点,鄙人孤陋寡闻,在亲身实践后才发现其中奥妙可参照一 ...

  3. hdu 2669 Romantic

    Romantic Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Submit Sta ...

  4. RedHat5.1下安装Seismic Unix44R1

    以前安装过好几次,在这里总结下.不足之处,欢迎批评指正. 用su44用户登录,修改环境变量(~/.bash_profile文件中添加) export CWPROOT=/home/`whoami`/cw ...

  5. js-DOM,DOM扩展

    DOM: 1. 了解节点的信息:nodeName(属性的标签名),nodeValue两个属性 在取得信息之前要进行判断是不是节点,节点类型由12个数值常量进行表示 2.每个节点都有一个childNod ...

  6. Codeforces 335C Sorting Railway Cars

    time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standa ...

  7. linux(centos6)搭建ftp服务器

    前提 ssh服务已经开启,关闭防火墙,主机和虚拟机能ping通 查看ssh和防火墙的状态 service sshd status service iptables status 开启ssh服务 ser ...

  8. Trie UVA 11732 "strcmp()" Anyone?

    题目传送门 题意:询问所有字符串的比较次数和(注意for循环内的比较也算) 分析:将所有字符串插入到字典树上,然后结点信息记录有几个字符串,那么每走到一个结点就能知道比较到此时需要的次数.学习到链表存 ...

  9. ORACLE使用EXPDP和IMPDP数据泵进行导出导入的方法

    ORACLE使用EXPDP和IMPDP数据泵进行导出导入的方法 (2010-05-28 12:54:34) http://blog.sina.com.cn/s/blog_67d41beb0100ixn ...

  10. BZOJ4384 : [POI2015]Trzy wieże

    首先只有一种字符的情况可以通过双指针在$O(n)$的时间内处理完毕. 设$cnt[i][j]$表示前$i$个字符中$j$字符出现的次数,那么对于两个位置$j<i$: 如果 $cnt[i][0]- ...