servlet的生命周期

时间:2022-12-23 00:15:42

servlet的特性

servlet的生命周期使得servlet能够处理CGI的性能和资源问题,以及低级别服务器API编程的安全性问题。servlet容器通常在一个JVM中执行多个servlet,能够高效方便地共享资源。同时由于Java语言的特性,能够避免相互访问私有数据。servlet能够像处理实例对象一样处理JVM中的请求,这种方法比使用完全的进程要节省很多内存,同时能够高效地访问外部资源。
servlet的生命周期非常灵活,servlet容器唯一需要严格遵守的规则是以下的生命周期约定:
1. 创建、初始化servlet;
2. 处理0个或多个客户端的服务请求;
3. 销毁servlet并进行垃圾回收。

只用一个Java虚拟机

实例的持续性

servlet像对象实例一样在请求之间保持持续性。也就是说,当servlet的代码被载入时,服务器生成一个实例,这个实例处理这个servlet的所有请求。它在三个方面提高了性能:
1. 内存消耗变少;
2. 节省生成一个servlet对象所需要的资源,当一个请求进入时,serlvet已经载入完毕,可以马上执行;
3. 保证持续性。一个servlet可能会把处理一个请求的所有可能用到的资源全部载入。例如,一个数据库连接可被打开一次而多次使用。这个连接甚至可以被多个servlet使用。(???
servlet不仅在请求之间保持连续性,对其生成的线程也是如此。可以令一个线程来计算,另一个线程进行结果的显示。

一个整体计数器

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HolisticCounter extends HttpServlet {

static int classCount = 0; // shared by all instances
int count = 0; // separate for each servlet
static Hashtable instances = new Hashtable(); // also shared

public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
res.setContentType("text/plain");
PrintWriter out = res.getWriter();

count++;
out.println("Since loading, this servlet instance has been accessed " +
count + " times.");

// Keep track of the instance count by putting a reference to this
// instance in a hashtable. Duplicate entries are ignored.
// The size() method returns the number of unique instances stored.
instances.put(this, this);
out.println("There are currently " +
instances.size() + " instances.");

classCount++;
out.println("Across all instances, this servlet class has been " +
"accessed " + classCount + " times.");
}
}

用count变量统计HolisticCounter这个类的访问次数,用classCount统计共享的访问次数,用instance统计实例数量。(如何产生多个servlet?不是一个容器里面用一个servlet可以处理各种请求了吗?微服务是怎样的?

init和destroy

服务器在构造完servlet实例和处理任何请求之前运行servlet的init()方法,在servlet不用服务了或者所有挂起的请求处理完毕或超时时调用destroy()方法。
一个servlet的初始化参数在web.xml中,如

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">


<web-app>
<servlet>
<servlet-name>
counter
</servlet-name>
<servlet-class>
InitCounter
</servlet-class>
<init-param>
<param-name>
initial
</param-name>
<param-value>
1000
</param-value>
<description>
The initial value for the counter <!-- optional -->
</description>
</init-param>
</servlet>
</web-app>

将多行的< init-param >放在< servlet >标签中。
在destroy()方法中,servlet应释放所有它占用的无法回收的所有存储单元,或者用于保存所有未保存的信息,或者为了保持持续性而要写入init()方法中以供下次初始化时读入的信息。

一个带初始化参数的计数器

public class InitCounter extends HttpServlet {

int count;

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

public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
res.setContentType("text/plain");
PrintWriter out = res.getWriter();
count++;
out.println("Since loading (and with a possible initialization");
out.println("parameter figured in), this servlet has been accessed");
out.println(count + " times.");
}
}

带有init和destroy的计数器

保持载入之间的连续性–一个不会重新开始的计数器。
晕死。。。满心欢喜地看代码。。。。居然是在destroy()里面把参数写到文件里面,然后在init()里面读文件内容。。。。
如果服务器中途崩溃,那么destroy()方法不会被调用。

单线程模式

一般情况下,每个servlet注册名中只有一个servlet实例,但也有可能servlet为每个名字生成一组实例,这些实例共同处理请求(这个怎么操作?)。
这些servlet通过javax.servlet.SingelThreadModel接口实现,这个是一个空的“标签”,不定义任何方法或变量,只是服务器用来标志选择生命周期的servlet。
一个载入了SingleThreadModel servlet的服务器,必须保证servlet的service方法中没有同时执行的两个线程,所以必然是线程安全的。
servlet的生命周期

注意,这样可能会让服务器产生过多的实例,应尽量避免使用。。。。。。
最好还是用线程池开多线程。

启动时载入

可以配置servlet的web.xml,使得服务器一开始就载入servlet(开始init()),通过< load-on-startup >标签实现。如下:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">


<web-app>
<servlet>
<servlet-name>
ps
</servlet-name>
<servlet-class>
PrimeSearcher
</servlet-class>
<load-on-startup/>
</servlet>
</web-app>

这个配置文件告诉服务器以注册名Ps创建一个primeSearch实例,并在服务器启动时用Init()初始化servlet。servlet能够被 URL/servlet/ps 所访问。注意,处理 URL/servlet/PrimeSearcher的servlet实例并没有在启动时被载入(没看懂?)。
这里< load-on-startup >标签是空的,如果带有数字,表示载入时的顺序,从小到大被载入。若为负数或非整数,可在启动的任何时刻被载入,取决于服务器规定的顺序。

客户端缓存

并不是所有请求都会调用doget()方法。只有在servlet输出改变时才向浏览器报告。这时调用的是getLastModified()方法。
大多数web服务器,它们会返回一个文档,其中包括响应的一部分:Last-Modified首部,例如:

Tue, 06-May-98 15:41:02 GMT

这个首部在它被浏览器下载时告诉服务器网页的Last-Modified时间,服务器根据这个判断给定时间文件是否有改动。如果有改动,服务器需要发送新的内容,否则,服务器告诉浏览器网页未变,可以重新显示缓存的内容。
这项技术对静态网页很有作用。

服务端缓存

为了实现服务端缓存,一个servlet必须:
1. 用扩展com.oreilly.servlet.CacheHttpServlet 代替 HttpServlet
2. 实现 getLastModified(HttpServletRequet)方法

关于客户端和服务端的缓存,可以参考这篇博客