第 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. js循环添加事件

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. T-SQL优化

    我们做软件开发的,大部分人都离不开跟数据库打交道,特别是erp开发的,跟数据库打交道更是频繁,存储过程动不动就是上千行,如果数据量大,人员流动大,那么我么还能保证下一段时间系统还能流畅的运行吗?我么还 ...

  3. 【转】saiku与kylin整合备忘录

    http://blog.csdn.net/freefishly/article/details/51759133 为什么要整合? Kylin是通过离线预计算将Hive中事实表的各组合维度的值存储在Hb ...

  4. Nginx支持多站点配置小结

    如何配置 web 服务器才能在一个 VPS 上放置多个网站/博客呢?如何通过一个 IP 访问多个站点/域名呢?这是大多数 web 服务器支持的 virtual hosting 功能.即一个IP对应多个 ...

  5. express-10 表单处理

    从用户那里收集信息的常用方法就是使用HTML表单.无论是使用浏览器提交表单,还是使用AJAX提交,或是运用精巧的前端控件,底层机制通常仍旧是HTML表单. 向服务器发送客户端数据 向服务器发送客户端数 ...

  6. BZOJ 2002 [Hnoi2010]Bounce 弹飞绵羊 ——Link-Cut Tree

    [题目分析] 以前用分块的方法做过这道题目,现在再用LCT水一边,发现思路确实巧妙. 每次弹射,可以看作在一条边上走了过去,而且很重要的性质,每一个点的出边只有一条. 那么就很容易知道,可以用LCT维 ...

  7. wpf中手风琴控件Accordion编辑模板后控件不正常。

    昨天有个网友Accordion控件从sl迁移到wpf时候显示不正常.也是就没有效果. 我也是sl做的比较多,wpf玩的少,Accordion模板里触发器,状态组调了一早上都没达到满意效果, 无奈只有百 ...

  8. 三进制状压 HDOJ 3001 Travelling

    题目传送门 题意:从某个点出发,所有点都走过且最多走两次,问最小花费 分析:数据量这么小应该是状压题,旅行商TSP的变形.dp[st][i]表示状态st,在i点时的最小花费,用三进制状压.以后任意进制 ...

  9. BZOJ3898 : 打的士

    设$f_i$表示选择的答案区间左端点为$i$时,区间长度最小是多少. 那么每来一批人的时候,设$nxt$为$i$右边最近的一个可行决策,则$f_i=\max(f_i,nxt-i)$. 注意到$f$的形 ...

  10. 【NOI2011】道路修建 BFS

    [NOI2011]道路修建 Description 在 W 星球上有 n 个国家.为了各自国家的经济发展,他们决定在各个国家之间建设双向道路使得国家之间连通.但是每个国家的国王都很吝啬,他们只愿意修建 ...