Servlet学习

来源:互联网 发布:kanahei知乎 编辑:程序博客网 时间:2024/06/10 04:45

一.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 遵循的过程:

  1. 初始化阶段 - 调用init()方法
  2. 响应客户请求阶段 - 调用service()方法
  3. 终止阶段 - 调用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 何时调用

  1. 每次服务器接收到一个 servlet 请求时,服务器会创建一个新的线程,或从线程池分配一个线程,生成一对新的请求对象ServletRequest和响应对象ServletResponse
  2. 调用该servlet的service(ServletRequest,ServletResponse)方法,参数为容器创建的请求和响应对象。
  3. service方法从ServletRequest对象获得客户请求信息
  4. service() 方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用 doGet、doPost、doPut、doDelete 等方法 ,处理该请求
  5. 通过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>

  1. 必需标签
  2. 可以任意取字符串值,但必须保证该名称在web.xml中唯一,该名称供其它的标签使用:<servlet-mapping>、<filter>等

4.1.2  <serlet-name>

  1. servlet实现类的完整路径:com.study.servlet.TestServlet
  2. 通过类路径找到对应的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 {// 不更新页面,返回状态码304resp.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);elseresp.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);elseresp.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);elseresp.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);elseresp.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 */