一.Servlet概述
1.1 什么是Servlet
- Java servlet 是运行在 Web 或应用服务器上的程序,作为在来自 Web 浏览器或其他 HTTP 客户机的请求和在HTTP 服务器上的数据库或应用程序的中间层。
- Java Servlet 是运行在 Web 服务器上的 Java 类,在 Web 服务器上有一个支持 Java Servlet 规范的解释器。
- Servlet 可以使用 javax.servlet 和 javax.servlet.http 包来创建。它们是 Java 企业版的一个标准部分,也是支持大型开发项目的 Java 类库的扩展版。
- 就像任何其他 Java 类一样,Java Servlet 可以创建和编译。可以使用 JDK 的 Java 编译器或其他任何当前编译器来编译 Servlet。
1.2 Servlet架构
1.3 Servlet任务
1.3.1 处理前
- 读取由客户端(浏览器)发送的显式数据。这包括网页上的 HTML 表单,或者也可以是来自自定义的 HTTP 客户端程序的表单。
- 读取由客户端(浏览器)发送的隐式 HTTP 请求数据。这包括 cookies、媒体类型和浏览器能理解的压缩格式等等。
1.3.2 处理时
- 处理数据并生成结果。这个过程可能需要访问数据库,执行 RMI 或 CORBA 调用,调用 Web 服务,或者直接计算响应
1.3.3 处理后
- 发送显式数据(即文档)到客户端(浏览器)。该文档可以以多种多样的格式被发送,包括文本文件(HTML 或 XML)、二进制文件(GIF 图像)、Excel 等
- 发送隐式的 HTTP 响应到客户端(浏览器)。这包括告诉浏览器或其他客户端被返回的文档类型(例如 HTML),设置 cookies 和缓存参数,以及其他类似的任务
二.Servlet生命周期
Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程:
- 初始化阶段 - 调用init()方法
- 响应客户请求阶段 - 调用service()方法
- 终止阶段 - 调用destroy()方法
2.1 init()
init ()方法被设计成只调用一次。它在第一次创建 servlet 时被调用,在后续每次用户请求时不再调用。因此,它用于一次性初始化
2.1.1 何时初始化
有两种情况Servlet容器会加载Servlet实现类,该Servlet类的无参构造函数运行,初始化该Servlet
- 当用户第一次调用对应于该 servlet 的 URL 时,servlet 被创建
- 在web.xml中配置该Servlet的<load-on-startup>属性,Servlet容器启动时加载该Servlet,<load-on-startup>1</load-on-startup>表示servlet容器启动时就加载该servlet
2.1.2 何时调用
- 在第一次创建 Servlet 实例时被调用,在后续每次用户请求时不再调用。
- 该servlet实例创建后,并在该Servlet实例能为客户请求提供服务之前,servlet容器要对servlet调用init().
- 可以重写javax.servlet.GenericServlet.java中的init()方法,添加一些自定义初始化代码。
2.2.1 调用次数
- init ()方法只能调用一次,它在第一次创建
servlet 时被调用
2.1.4 为什么只有一个init()
构造函数难道不足以初始化servlet吗?在init()方法中会放什么代码?
- 其实构造函数只是使servlet实现类成为一个普通的对象,而不是一个servlet,要想成为一个servlet,对象必须具备一些"servlett特性"。如能够记录事件日志、得到其他资源的引用、保存其他servlet的属性、能够使用ServletContext引用从容器得到信息等
- init()方法使servlet可以访问ServletConfig和ServletContext对象,servlet需要从这些对象获取servlet配置和web应用的信息
2.2 service()
service() 方法是执行实际任务的主要方法。Servlet 容器(即 web 服务器)调用 service() 方法来处理来自客户端(浏览器)的请求,并将格式化的响应写回到客户端
2.2.1 何时调用
- 每次服务器接收到一个 servlet 请求时,服务器会创建一个新的线程,或从线程池分配一个线程,生成一对新的请求对象ServletRequest和响应对象ServletResponse
- 调用该servlet的service(ServletRequest,ServletResponse)方法,参数为容器创建的请求和响应对象。
- service方法从ServletRequest对象获得客户请求信息
- service() 方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用 doGet、doPost、doPut、doDelete 等方法 ,处理该请求
- 通过ServletResponse对象向客户返回响应信息
2.2.2 调用次数
- 每次服务器接收到一个 servlet 请求时,服务器会产生一个新的线程并调用服务
2.2.3 需要重写吗
- service() 方法由servlet容器调用,且 service 方法在适当的时候调用 doGet、doPost、doPut、doDelete 等。所以对 service() 方法你什么都不需要做
- 只是根据接收到的来自客户端的请求类型来重写 doGet() 或 doPost()
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);
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
if (method.equals("GET")) {
long lastModified = getLastModified(req);
if (lastModified == -1L) {
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified / 1000L * 1000L) {
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals("POST")) {
doPost(req, resp);
} else if (method.equals("PUT")) {
doPut(req, resp);
} else if (method.equals("DELETE")) {
doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
doOptions(req, resp);
} else if (method.equals("TRACE")) {
doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
2.3 destroy()
destroy() 方法只在 servlet 生命周期结束时被调用一次 ,在调用 destroy() 方法之后,servlet 对象被标记用于垃圾回收
2.3.1 何时调用
- 当WEB应用被终止
- 或Servlet容器终止运行
- 或Servlet容器重新装载Servlet新实例时
2.3.2 调用次数
- destroy() 方法只在 servlet 生命周期结束时被调用一次
2.3.3 调用目的
调用Servlet的destroy()方法,在destroy()方法中可以释放掉Servlet所占用的资源
- 关闭数据库连接
- 停止后台线程
- 将 cookie 列表或点击计数器写入磁盘,并执行其他类似的清理活动
三.创建Servlet实例
直接实现servlet接口来编写Servlet很不方便,需要实现的方法太多,在JDK中javax.servlet.*,javax.servlet.http.*包下提供了对servlet的支持。
编写Servlet时直接继承HttpServlet,并覆盖需要的方法即可,一般覆盖doGet()和doPost()方法
package com.study.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 一个Servlet实现类
*/
public class TestServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public TestServlet() {
super();
}
/**
* 以get方式访问页面时执行此方法
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.sendRedirect("test.jsp");
}
/**
* 以get方式访问页面时执行此方法
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
四.Servlet配置
光有Servlet类还不行,web容器必须知道浏览器怎么访问这个Servlet,也就是需要在web.xml配置Servlet的类文件与访问方式
<servlet>
<description>Servlet配置</description>
<display-name>TestServlet</display-name>
<servlet-name>TestServlet</servlet-name>
<servlet-class>com.study.servlet.TestServlet</servlet-class>
<init-param>
<description>编码</description>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>TestServlet</servlet-name>
<url-pattern>/test.do</url-pattern>
</servlet-mapping>
4.1 <servlet>标签
4.1.1 <display-name>
- 必需标签
-
可以任意取字符串值,但必须保证该名称在web.xml中唯一,该名称供其它的标签使用:<servlet-mapping>、<filter>等
4.1.2 <serlet-name>
- servlet实现类的完整路径:com.study.servlet.TestServlet
- 通过类路径找到对应的servlet类文件
4.1.3 可选标签
- <init-param> : 配置一个初始化的参数,包括一个参数名称(<param-name>)和一个参数值(<param-value>)
- Servlet中使用getServletContext().getInitParam(String paramName)方法来获取配置的初始化参数。
- <load-on-startup> : 配置该servlet的加载方式。可选值为0和1.
- 如果配置为1,tomcat会在启动时就加载该servlet
- 如果配置为0,tomcat会在第一次请求该servlet时才加载该servlet
- Spring、Struts等框架中会使用该参数来预加载框架中核心的Servlet
4.2 <servlet-mapping>标签
配置好<servlet>后还需要配置<servlet-mapping>,即配置该Servlet的访问方式,访问方式使用<servlet-mapping>配置,
4.2.1 <servlet-name>
跟<servlet>中的<servlet-name>对应,指明采用该访问方式的Servlet的名称
4.2.2 <servlet-mapping>
以"/"开头,"/"表示上下文路径,如:
<servlet-mapping>
<servlet-name>TestServlet</servlet-name>
<url-pattern>/test.do</url-pattern>
</servlet-mapping>
- <url-pattern>中允许使用通配符"*"、“?”,“*”表示任意长度的字符串。
- 从JDK 5起,<servlet-mapping>中可以配置多个<url-pattern>,如
<servlet-mapping>
<servlet-name>TestServlet</servlet-name>
<url-pattern>/test.do</url-pattern>
<url-pattern>/test.html</url-pattern>
<url-pattern>/test.jsp</url-pattern>
<url-pattern>/test.php</url-pattern>
<url-pattern>/test.asp</url-pattern>
</servlet-mapping>
这可以实现隐藏编程语言的母的,客户无法从url上知道该程序是用php或asp或其他语言写的。
五.访问
5.1 创建界面
test.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>欢迎</title>
</head>
<body>
Hello World!
</body>
</html>
5.2 创建Servlet
5.3 访问路径
http://localhost:8080/servlet2/test.do
六.Servlet类结构
6.1 类结构
6.2 Servlet接口
package javax.servlet;
import java.io.IOException;
public abstract interface Servlet {
/**
* 仅执行一次
*/
public abstract void init(ServletConfig paramServletConfig) throws ServletException;
public abstract ServletConfig getServletConfig();
/**
* 每次请求容器都会创建一个线程,调用service()
*/
public abstract void service(ServletRequest paramServletRequest, ServletResponse paramServletResponse) throws ServletException, IOException;
public abstract String getServletInfo();
/**
* 结束servlet生命
*/
public abstract void destroy();
}
/* Location: D:\...\apache-tomcat-6.0.20\lib\servlet-api.jar
* Qualified Name: javax.servlet.Servlet
* Java Class Version: 5 (49.0)
* JD-Core Version: 0.5.3
*/
6.3 GenericServlet抽象类
package javax.servlet;
import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
// servlet配置对象
private transient ServletConfig config;
// 仅调用一次
public void init(ServletConfig config) throws ServletException {
this.config = config;
init();
}
// 可以被重写,执行一些初始化代码
public void init() throws ServletException {
}
// 每次请求到来,容器创建一个线程,调用此方法
public abstract void service(ServletRequest paramServletRequest, ServletResponse paramServletResponse) throws ServletException, IOException;
public void destroy() {
}
/**
* 根据name获取servlet在web.xml中初始化配置属性
*/
public String getInitParameter(String name) {
return getServletConfig().getInitParameter(name);
}
public Enumeration getInitParameterNames() {
return getServletConfig().getInitParameterNames();
}
public ServletConfig getServletConfig() {
return this.config;
}
/**
* 获取servlet容器上下文对象
*/
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}
public String getServletInfo() {
return ;
}
public void log(String msg) {
getServletContext().log(getServletName() + ": " + msg);
}
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
public String getServletName() {
return this.config.getServletName();
}
}
/* Location: D:\.....\apache-tomcat-6.0.20\lib\servlet-api.jar
* Qualified Name: javax.servlet.GenericServlet
* Java Class Version: 5 (49.0)
* JD-Core Version: 0.5.3
*/
6.4 HttpServlet抽象类
package javax.servlet.http;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.ResourceBundle;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* 一般Servlet实现类继承此类,重写doGet()和doPost()方法
*/
public abstract class HttpServlet extends GenericServlet implements Serializable {
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET"; // get方法
private static final String METHOD_OPTIONS = "OPTIONS";
private static final String METHOD_POST = "POST"; // post方法
private static final String METHOD_PUT = "PUT";
private static final String METHOD_TRACE = "TRACE";
private static final String HEADER_IFMODSINCE = "If-Modified-Since";
private static final String HEADER_LASTMOD = "Last-Modified";
private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings");
// 每次请求,容器创建一个线程,调用此方法
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);
}
// 不应该重写此方法,根据method调用对应方法
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
if (method.equals("GET")) {
// 获取上次请求时间
long lastModified = getLastModified(req);
if (lastModified == -1L) {// 为负表示再次请求该页面,更新
doGet(req, resp);
} else {
// 从请求头中获取
long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified / 1000L * 1000L) {
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
// 不更新页面,返回状态码304
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals("POST")) {
doPost(req, resp);
} else if (method.equals("PUT")) {
doPut(req, resp);
} else if (method.equals("DELETE")) {
doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
doOptions(req, resp);
} else if (method.equals("TRACE")) {
doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_post_not_supported");
if (protocol.endsWith("1.1"))
resp.sendError(405, msg);
else
resp.sendError(400, msg);
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1"))
resp.sendError(405, msg);
else
resp.sendError(400, msg);
}
/**
* 一般情况下,浏览器都会缓存已经访问过的页面内容,getLastModified方法的返回值可以影响浏览器如何处理和利用缓存内容
* 可以重写此方法
* 返回值为负表示再次请求该页面
*/
protected long getLastModified(HttpServletRequest req) {
return -1L;
}
protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
NoBodyResponse response = new NoBodyResponse(resp);
doGet(req, response);
response.setContentLength();
}
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_put_not_supported");
if (protocol.endsWith("1.1"))
resp.sendError(405, msg);
else
resp.sendError(400, msg);
}
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_delete_not_supported");
if (protocol.endsWith("1.1"))
resp.sendError(405, msg);
else
resp.sendError(400, msg);
}
private static Method[] getAllDeclaredMethods(Class c) {
if (c.equals(HttpServlet.class)) {
return null;
}
Method[] parentMethods = getAllDeclaredMethods(c.getSuperclass());
Method[] thisMethods = c.getDeclaredMethods();
if ((parentMethods != null) && (parentMethods.length > 0)) {
Method[] allMethods = new Method[parentMethods.length + thisMethods.length];
System.arraycopy(parentMethods, 0, allMethods, 0, parentMethods.length);
System.arraycopy(thisMethods, 0, allMethods, parentMethods.length, thisMethods.length);
thisMethods = allMethods;
}
return thisMethods;
}
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Method[] methods = getAllDeclaredMethods(super.getClass());
boolean ALLOW_GET = false;
boolean ALLOW_HEAD = false;
boolean ALLOW_POST = false;
boolean ALLOW_PUT = false;
boolean ALLOW_DELETE = false;
boolean ALLOW_TRACE = true;
boolean ALLOW_OPTIONS = true;
for (int i = 0; i < methods.length; ++i) {
Method m = methods[i];
if (m.getName().equals("doGet")) {
ALLOW_GET = true;
ALLOW_HEAD = true;
}
if (m.getName().equals("doPost"))
ALLOW_POST = true;
if (m.getName().equals("doPut"))
ALLOW_PUT = true;
if (m.getName().equals("doDelete")) {
ALLOW_DELETE = true;
}
}
String allow = null;
if ((ALLOW_GET) && (allow == null)) allow = "GET";
if (ALLOW_HEAD)
if (allow == null) allow = "HEAD";
else allow = allow + ", HEAD";
if (ALLOW_POST)
if (allow == null) allow = "POST";
else allow = allow + ", POST";
if (ALLOW_PUT)
if (allow == null) allow = "PUT";
else allow = allow + ", PUT";
if (ALLOW_DELETE)
if (allow == null) allow = "DELETE";
else allow = allow + ", DELETE";
if (ALLOW_TRACE)
if (allow == null) allow = "TRACE";
else allow = allow + ", TRACE";
if (ALLOW_OPTIONS) {
if (allow == null) allow = "OPTIONS";
else allow = allow + ", OPTIONS";
}
resp.setHeader("Allow", allow);
}
protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String CRLF = "\r\n";
String responseString = "TRACE " + req.getRequestURI() + " " + req.getProtocol();
Enumeration reqHeaderEnum = req.getHeaderNames();
while (reqHeaderEnum.hasMoreElements()) {
String headerName = (String)reqHeaderEnum.nextElement();
responseString = responseString + CRLF + headerName + ": " + req.getHeader(headerName);
}
responseString = responseString + CRLF;
int responseLength = responseString.length();
resp.setContentType("message/http");
resp.setContentLength(responseLength);
ServletOutputStream out = resp.getOutputStream();
out.print(responseString);
out.close();
}
private void maybeSetLastModified(HttpServletResponse resp, long lastModified) {
if (resp.containsHeader("Last-Modified"))
return;
if (lastModified >= 0L)
resp.setDateHeader("Last-Modified", lastModified);
}
}
/* Location: D:\...\apache-tomcat-6.0.20\lib\servlet-api.jar
* Qualified Name: javax.servlet.http.HttpServlet
* Java Class Version: 5 (49.0)
* JD-Core Version: 0.5.3
*/