Servlet 介绍 以及Servlet生命周期(详细)

在上一篇文档里提到了CGI和Servelt 的对比,并介绍了在JAVA编写的应用程序Servlet在服务器上运行主要就是解决了CGI进程处理占资源效率高的问题。

Servlet 只是一个名词,具体的解释就是使用JAVA编写的一个应用程序,在服务器上运行,处理请求的信息,并且将处理后的结果回发的客户端。


对于所有的来自于客户端的请求,这段程序即Servlet只需要创建一次实例,因此它节省了大量的内存。Servlet在初始化后即停驻在内存中,因此每次做出请求时无需重新加载。


具体来看看Tomcat容器即Servlet容器到底是如何管理Servlet的,它的生命周期到底是如何运行的,其中是如何与Tomcat容器实现交互,主要大家认真理解下面的分析,基本上理解Servlet生命周期也是没有问题的。



[color=green][b]一.Tomcat 是如何加载Servlet的(Servlet的初始化)[/b][/color]

还是以Simon 本人配置的环境举例

找到 [url]C:\Program Files\Apache Software Foundation\Tomcat 6.0\webapps\myapp\WEB-INF[/url]下的 web.xml

这个配置文件实际上起到的最直接的作用就是管理Servlet. 在其中先加上两个


web.xml – 继续第一次测试Servlet 的配置时的内容,复制替换即可,加的部分用红色标识



PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">


My web application

A application for test!


ServletDemo
servletDemo


0

initial
10000

servletTest
MyServlet



servletTest
/servletpage



servletDemo
/t238servlet



新添的一个Servlet ServletDemo.java 也应该先编译好,然后放到

[url]C:\Program Files\Apache Software Foundation\Tomcat 6.0\webapps\myapp\WEB-INF\classes[/url]目录下,此时这个目录应该有 [color=olive]ServletDemo.class [/color]和 [color=olive]MyServlet.class [/color]两个类文件。

[url]ServletDemo.java[/url]
import javax.servlet.*;

import javax.servlet.http.*;
import java.io.*;
import java.util.*;
/**
*@author : Simon lv
演示Servlet的生命周期
继承HttpServlet
ServletDemo
*/
public class ServletDemo
extends HttpServlet {
//定义好将回发客户端页面的MIME类型,HTML格式的网页
private static final String CONTENT_TYPE = "text/html; charset=GBK";
//计数器
int count;
/**
* init 初始化方法第一次加载只会初始化一次一旦调用即说明了已经创建了ServeltDemo的实例,此后这个实例
* /将一直停留在Tomcat分配的一块内存中,下次有客户端的请求,就不用重复的实例化
* ServletConfig config 参数用来传递Servlet 在web.xml中的配置信息自动封装信息
*/
public void init(ServletConfig config) throws ServletException {
super.init(config);

//得到配置信息中initial参数的值
String initial = config.getInitParameter("initial");
try{
//转化为数字即计数器
count = Integer.parseInt(initial);
}catch(NumberFormatException e){
count =0;
}
//输出初始化信息
System.out.println("ServletDemo中的记数器Servlet 已经初始化成功!");
}

/**
* doGet 处理
*/
public void doGet(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
response.setContentType(CONTENT_TYPE);
PrintWriter out = response.getWriter();
out.println("");
out.println("LifeServletDemo");
out.println("");
count++;
out.println("自从加载后(读取参数化后)");
out.println("ServletDemo 已经被访问过了");
out.println(count+" 次");
out.println("");
out.println("");
out.close();

System.out.println("ServletDemo中的的doGet 方法被执行一次");
}

//doPost()方法
public void doPost(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
doGet(request, response);
}

//清除资源
public void destroy() {
System.out.println("ServletDemo 实例资源已经释放!");
}
}


[color=green][b]
二.启动Tomcat服务[/b][/color]

[url]C:\Program Files\Apache Software Foundation\Tomcat 6.0\bin tomcat6.exe[/url]

注意红色圈起来的部分

[u]图片1见附件[/u]


这句话的输出即证明了 在MyServlet中的 init方法被调用了一次!

MyServlet.java中的相关代码:

//初始化方法第一次加载只会初始化一次

public void init(ServletConfig config) throws ServletException {
super.init(config);
String initial = config.getInitParameter("initial");
try{
count = Integer.parseInt(initial);
}catch(NumberFormatException e){
count =0;
}
System.out.println("MyServlet 中的记数器Servlet 已经初始化成功!");

}


但是我在MyServlet.java 和上面新加的ServletDemo.java中都存在 init()方法,但是为什么仅仅MyServlet.java中的方法被调用了呢?原因就在于在 web.xml中,指定了Tomcat一启动就初始化的实例



0

initial
10000

servletTest
MyServlet



这样就通知了Tomcat在它的服务一启动的时候,就要去加载所对应的Servlet-class MyServlet这个类,0代表级别,随后是1.2.3.4---- 0最高。Tomcat首先把这些类加载并实例化,保存在自己的Servlet容器池中,如果以后有请求,就直接从容器池中取出来,处理相应的请求。

要注意的是:此时并没有来自于客户端的请求,是先于客户请求直接就已经加载完毕了的。


下面我们去请求 ServletDemo ,它没有像MyServlet 那样在客户端请求前就被Tomcat实例化了的,它需要由请求完成实例化。

在IE浏览器中输入 :[url]http://localhost:8080/myapp/t238servlet[/url]

在页面上看到

[u]图片2见附件[/u]


在Tomcat 控制台上看到: 注意红线部分!

[u]图片2见附件[/u]

说明了什么?

说明了,Tomcat加载Servlet实例至少有两种方法,一种是通过在 web.xml配置文件中设置加载级别,另一种是通过客户端访问到某个具体的Servlet时,也一定会完成初始化,加载这个实例!一旦实例被加载,后面的请求,将不会再重新实例化Servlet。

再刷新页面:[url]http://localhost:8080/myapp/t238servlet[/url]
对应的网页 和Tomcat

[u]图片3见附件[/u]

ServletDemo中的doGet方法被执行一次 多输出了一句,但是 初始化的信息没有再输出,说明了 init()方法只执行了一次,也即说明了Servlet实例只被Tomcat 加载了一次!


换个页面请求一次 [url]http://localhost:8080/myapp/servletpage[/url]相应页面和Tomcat控制台为:

[u]图片4见附件[/u]

MyServlet 的doGet()方法被执行了一次。

Init()方法呢? 答案是 MyServlet 在客户端请求之前就已经被Tomcat加载,还记得这段代码吗?


0

initial
10000

servletTest
MyServlet


目前只能知道是这么种情况,但是为什么输入不同的URL就能访问到不同的Servlet,服务器端如何判断来自客户端的请求要做什么,要交给那个JAVA程序去处理呢?并且 ServletDemo 即第一个页面是访问了1次,但是MyServlet的页面为10001次,这些都是从哪里得到的?


继续…..

打开 web.xml 配置文件 选两段内容


servletDemo
/t238servlet

因为我们部署在Tomcat上的JSP环境是 myapp 所以我们在访问服务器时,myapp是要写上的,就相当于一个根目录。

[url]http://localhost:8080/myapp/t238servlet[/url] 这是一个请求的网址。

当客户端提交这个请求时,服务器会对这个网址进行分析,首先会通过 myapp 定位到相应的根目录,再通过 /t238servlet 确定要访问的资源。

在配置文件中

servletDemo

/t238servlet

服务器端就已经明确的告诉了程序 需要用一个名叫 servletDemo 的程序去处理这个请求。

于是….. servlet-name 就可以定位到真正的可以处理客户端请求的类 ServletDemo.class


ServletDemo
servletDemo


ServletDemo.class 在哪里? 就位于我们部署好的路径中 \webapps\myapp\WEB-INF\classes

注意,真正的处理程序是 ServletDemo.class

不同的请求,服务器通过 url-pattern 的设置,就能将请求交给相应的服务器端Servlet程序去处理,因此看到的效果就不一样,Tomcat控制台中显示的效果也就不一样。


第二个问题? 第一个页面显示的是访问1次,而后一个页面是10001次。原因在于 web.xml 配置文件中


0

initial
10000

servletTest
MyServlet

即MyServlet配置信息中有一个参数为 initial ,值为 10000

MyServlet中的初始化方法

//初始化方法第一次加载只会初始化一次

public void init(ServletConfig config) throws ServletException {
super.init(config);
String initial = config.getInitParameter("initial");
try{
count = Integer.parseInt(initial);
}catch(NumberFormatException e){
count =0;
}

System.out.println("MyServlet 中的记数器Servlet 已经初始化成功!");
}

页面输出部分:

out.println("自从加载后(读取参数化后)");

out.println("MyServlet 中的 已经被访问过了");

out.println(count+" 次");


相信看完这些就能基本上明白这种差异了,并且发现由于


ServletDemo
servletDemo

没有这种初始化参数,自然就无法获得这种效果了。


但是随即在 以上代码中填入


initial
100


Ctrl+S 保存 web.xml 再次刷新 [url]http://localhost:8080/myapp/t238servlet[/url]马上就能看到


[u]图片5见附件[/u]
从100次开始了!!!


由于 web.xml 就是Tomcat管理Servlet的环境,Tomcat不需要关闭,只要 web.xml做出改动,它的能够识别到这种改动。

所以在上面就能够清楚的看到 :

ServletDemo实例资源已经释放,MyServlet 也是如此,即都调用了它们的

[url] //清除资源
public void destroy() {
System.out.println("MyServlet 实例资源已经被释放!");

}[/url]
方法。

并且重新初始化并加载 优先级别为0的 MyServlet,同时因为重新刷新了http://localhost:8080/myapp/t238servlet 由 ServletDemo处理这个请求,自然ServletDemo先实例化,并被加载,同时因为重新制定了初始化参数,所以效果自然也变成 101次访问!

[color=green][b]
三.再述Servlet生命周期及关键方法的调用,以及参数和内容部分的详解。[/b][/color]Servlet的生命周期由Servlet容器控制,可以通过两种方式创建Servlet的实例:一是通过 web.xml配置文件首先加载某个Servlet,或者当客户端请求时动态加载某一个Servlet.这两种方式在前面已经提到并由具体事例证明。



首先来看Servlet ,Servlet是一个接口。位于 package javax.servlet; 包。

[color=green]第一.在这个接口中有几个重要的抽象方法:[/color]

public void init(ServletConfig config) throws ServletException;

--- The servlet container calls the init method exactly once after instantiating the servlet.

---Servlet容器一旦创建Servlet实例,就会调用 init()方法完成此Servlet的初始化,有点类似于普通JAVA类的构造方法一样,创建实例时就完成对这个实例的初始化,也有可以什么都不做。

但是里面有一个参数 ServletConfig config ,ServletConfig 其实也是一个接口,其意思是实现了ServletConfig 接口的对象作为参数进行传递。

ServletConfig 接口中有几个方法:
public String getServletName(); the name of the servlet instance 返回Servlet实例的名称

public ServletContext getServletContext(); 返回与容器交互的上下文信息
public String getInitParameter(String name); 得到初始化参数的信息
public Enumeration getInitParameterNames(); //枚举值 参数的信息

简单一点,config对象就可以理解成为我们在Tomcat容器环境下的配置文件对象。就像我们从 web.xml中设置好初始化参数,随后又能在 init()方法中取得一样。

  public void init(ServletConfig config) throws ServletException {
super.init(config);
String initial = config.getInitParameter("initial");
try{
count = Integer.parseInt(initial);
}catch(NumberFormatException e){
count =0;
}
System.out.println("MyServlet 中的记数器Servlet 已经初始化成功!");
}


[color=green]第二.看一个很重要的方法 service( )方法[/color]

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

它的任务就是在Servlet实例化之后,就可以处理客户端发出的请求,最终向客户端回发响应。

@param req the ServletRequest object that contains
the client's request
@param res the ServletResponse object that contains
the servlet's response

两个参数:一个是包含有来自于客户端请求信息封装后的 req —— 一个实现了ServletRequest接口的对象

一个是包含有Servlet处理客户端请求后封装的 res —— 一个实现了 ServletResponse 接口的响应对象


[color=green]第三. public void destroy(); 如果没有需要处理的请求,就会释放Servlet实例。[/color]

整个流程是:

1. 实例化Servlet . 不管是来自于客户端请求,还是由 web.xml配置,总之Servlet容器首先要实例化Servlet,并加载到内存中。

2. 在实例化后,马上调用Servlet的init()方法,完成对实例的初始化。这两个步骤和JAVA类先用 new 关键字创建实例,在堆区分配内存,并通过构造方法的完成对此实例对象初始化,修改堆区的属性值的道理是一样的。

3. 服务:由容器根据用户的请求分配相应的Servlet实例去处理请求,回发响应,这个过程是由 service()方法来完成的。

4. 释放资源 destory( );

要实例化一个Servlet,容器必须找到Servlet类,加载Servlet类并创建Servlet对象。

然后通过Servlet的 init()方法来初始化Servlet.ServletConfig 接口对象作为参数传递给 init()方法,该接口对象为Servlet提供对ServletContext 接口的访问,Servlet 容器使用ServletContext接口与Servlet容器进行通信。


如果初始化Servlet失败,则抛出UnavailableException或 ServletException异常,并再次尝试对Servlet进行实例化和初始化。然后将ServletRequest和 ServletResponse接口对象作为参数传递给service()方法,该方法将处理请求并返回响应。

如果响应请求时引发异常,则容器通过调用Servlet的 destory()方法来卸载实例,调用destory()方法后,就会从内存中释放Servlet实例。destory()方法给了Servlet机会来清除所持有的资源,比如内存,文件处理和线程,以确保在内存中所有的持续状态和Servlet的当前状态是同步的。


写到这里,再回头看我们的实例

public class ServletDemo 
extends HttpServlet { ……

public void doPost(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
doGet(request, response);
}


此时的我们自定义的Servlet 类,并没有实现Servlet而是实现的 HttpServlet ,且方法中也找不到service()方法,取而代之的是 doPost 和 doGet( )方法,参数类型和 service( )方法定义到有些类似,只不过一个是ServletRequest,一个是 HttpServletRequest.



这个地方就需要从类的继承关系上来说明了。

HttpServlet 是一个抽象类继承GenericServlet public abstract class HttpServlet extends GenericServlet

GenericServlet 也是一个抽象类,但是它实现了 abstract class GenericServlet
implements Servlet, ServletConfig 等接口 ,注意 Servlet也包括在其中。


那么,必须GenericServlet抽象类就会实现Servlet接口中的方法,如 init( ) , destory() 等方法,包括service()方法,不过这个方法并没有被实现。 如然后以抽象的形式存在,很明显service方法需要靠实现了GenericServlet的类去完成。

public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;

public abstract class HttpServlet extends GenericServlet

在HttpServlet抽象类还是完成了一些具体的内容 ,比如实现GenericServlet的 service( ) 方法

public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException{
}



不过在这个方法中,它同时完成了两个对象的强制转换,并将两个对象作为参数传给自己真正实现的service()方法。

public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
HttpServletRequest request;
HttpServletResponse response;

try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException("non-HTTP request or response");
}
service(request, response);
}


在HttpServlet中有两个 service 方法,上面的是第一个,实现了GenericServlet抽象类的抽象方法,第二个就是红色文字标识的,此 service()方法的重载版本。

protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException



其名称一样,但参数类型不一致,完成重载的 service()方法,才是HttpServlet类的关键流程控制与处理的核心方法。不过,在这个service()方法中,它只做了一件事情,根据HTTP请求头中的请求行的请求方法法来完成方法的转移,例如是 get方法还是post方法。



如果是 get方法,就交给 HttpServlet类中的 doGet()去处理,参数照传,如果是post方法就交给 doPost去处理。这样一来,从最开始的只要是请求就处理到现在的请求来了,还要看看请求是什么方法它再做出反应,于是就有各种各样的处理方法,而所有方法的参数倒还是一致的 。

HttpServletRequest req, HttpServletResponse resp 。

当然方法可不止 get,post 还有 post,contact 等其它6种方法。

因此,看到HttpServlet中的重载的 service()方法就是这样的:

protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String method = req.getMethod(){

if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < (lastModified / 1000 * 1000)) {

// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}


所以到这里应该不难理解,虽然我们提到的是Servlet生命周期中有一个重要的service方法,但是在我们自己实现的Servlet类中并没有看见,其原因就是我们实现的是HttpServlet类,这个类中的service方法首先已经可以通过类的继承关系一样得到了来自于客户端请求的请求对象,其次通过对请求方法的判断将service方法的控制流程交给的相对于单一的方法处理,例如doGet(),doPost()。 如果我们实现了HttpServlet类,还需要继续重写service方法的话,那也就意味着我们仍然需要去搞定这些请求方法的判断工作。所以,最简单最直接的就是直接重写 HttpServlet中的 doGet()或者doPost()方法,所有的问题就迎刃而解了。

HttpServlet 作为一个抽象类用来创建用户自己的HTTP Servlet.HttpServlet类扩展了GenericServlet 类。HttpServlet类的子类必须至少重写以下方法中的一个:doGet( ) / doPost( ) .

doGet( ): 由服务器调用来处理客户端发出的GET请求,通过GenericServlet类的service( ) 方法来调用此方法。重写GET方法还支持HTTP HEAD请求,该请求返回没有主体只有标题字段的响应。提交响应之前,Servlet容器要编写标题,这是因为在HTTP中必须在发送响应主体之前发送标题。

GET方法必须是安全的,如果客户端请求更改存储的数据,则必须使用其它的HTTP方法。

doPost() 处理post请求,其流程和上述差不多。

另外,应该要看看,请求头和响应头,即HTTP请求和响应的原理,本人会另起文章做出解释。

转载请注明出处 [url]http://lvp.iteye.com[/url]

你可能感兴趣的:(JSP和Servlet)