1. Servlet概述

Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层

Servlet 在 Web 应用程序中的位置:

Servlet技术(上) - 图1

Servlet 执行以下主要任务:

  • 读取客户端(浏览器)发送的显式的数据。这包括网页上的 HTML 表单,或者也可以是来自 applet 或自定义的 HTTP 客户端程序的表单。
  • 读取客户端(浏览器)发送的隐式的 HTTP 请求数据。这包括 cookies、媒体类型和浏览器能理解的压缩格式等等。
  • 处理数据并生成结果。这个过程可能需要访问数据库,执行 RMI 或 CORBA 调用,调用 Web 服务,或者直接计算得出对应的响应。
  • 发送显式的数据(即文档)到客户端(浏览器)。该文档的格式可以是多种多样的,包括文本文件(HTML 或 XML)、二进制文件(GIF 图像)、Excel 等。
  • 发送隐式的 HTTP 响应到客户端(浏览器)。这包括告诉浏览器或其他客户端被返回的文档类型(例如 HTML),设置 cookies 和缓存参数,以及其他类似的任务。

下图显示了一个典型的 Servlet 生命周期方案。

  • 第一个到达服务器的 HTTP 请求被委派到 Servlet 容器。
  • Servlet 容器在调用 service() 方法之前加载 Servlet。
  • 然后 Servlet 容器处理由多个线程产生的多个请求,每个线程执行一个单一的 Servlet 实例的 service() 方法。

Servlet技术(上) - 图2

2. Servlet API

2.1Servlet接口

Servlet接口时Servletde 核心,所有的Servlet类都必须实现这个接口,在Servlet接口中一共定义了5个方法,其中有三个方法都有Servlet容器来调用,容器会在Servlet的生命周期的不同阶段调用特定的方法。

方法名称 方法描述
init(ServletConfig servletConfig) 负责初始化Servlet对象,容器在创建号Servlet对象后会调用该方法
service(ServletRequest var1, ServletResponse var2) 负责响应客户的请求,为客户提供响应的服务,当容器接收到客户端要求访问的特定Servlet对象请求时,就会调用该Servlet对象的service()方法
destroy() 负责释放Servlet对象占用的资源,当Servlet对象结束生命周期时,容器会调用此方法
getServletConfig() 返回一个ServletConfig对象,该对象中包含了Servlet的初始化参数信息
getServletInfo() 返回一个字符串,在该字符串中包含了Servlet的创建者,版本和版权等信息

Servlet的生命周期简介

  1. 初始化阶段:
    1. Servlet容器加载Servlet类,把它的.class文件中的数据读入到内存中
    2. Servlet容器创建ServletConfig对象,
    3. Servlet容器创建Servlet对象
    4. Servlet容器调用Servlet对象的init()方法
  2. 运行阶段:每次用户请求到达时,Servlet容器调用一个线程去执行Servlet的Service方法
  3. 销毁阶段:当Web应用被终止时,会调用destory()方法,释放资源,比如文件流关闭和数据库连接关闭等

2.2 GenericServlet抽象类

GenericServlet抽象类为Servlet接口提供了通用实现,它与任何网络应用曾协议无关。它除了实现Servlet类之外,还实现了ServletConfig接口,和Serializable接口

从源码可以看到GenericServlet有两个主要特点

  1. 实现了init方法,GenericServlet类有一个ServletConfig类型的私有实例变量config,当调用init的时候,会将传入ServletConfig对象保存在私有实例变量中。
  1. private transient ServletConfig config;
  2. public void init(ServletConfig config) throws ServletException {
  3. this.config = config;
  4. this.init();
  5. }
  6. public void init() throws ServletException {
  7. }
  1. service方法是唯一没有实现的方法,如果要继承GenericServlet抽象类,必须实现service方法,提供具体服务
  1. public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
  1. destory方法是空实现,如果需要,继承后可以重写该方法
  1. public void destroy() {}
  1. 实现了ServletConfig接口中的所有方法,其实就是调用私有属性config的方法,这种写法叫做装饰者模式,为自己附加了一个servletConfig装饰身份,这种写法也可以达到继承的目的,并且可以比继承更加灵活一些,有兴趣可以课余扩展阅读设计模式。

    2.3 HttpServlet抽象类

    HttpServlet是GenericServlet的子类,提供了与Http协议相关的通用实现,它适合运行在与客户端采用HTTP协议通信的Servlet容器或者Web服务器中,在开发web应用中,自定义的Servlet类一般都是扩展(继承)HttpServlet类

Http协议将客户端请求分为了【GET】, 【POST】, 【DELETED】等多种方式,而HttpServlet针对每一种请求方式都提供了相应的服务方法,如【doGet】,【doPost】,【doPut】,【doDelete】

源码中httpServlet的service实现实际是调用它的一个重载service方法

  1. public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
  2. HttpServletRequest request;
  3. HttpServletResponse response;
  4. try {
  5. request = (HttpServletRequest)req;
  6. response = (HttpServletResponse)res;
  7. } catch (ClassCastException var6) {
  8. throw new ServletException("non-HTTP request or response");
  9. }
  10. this.service(request, response); //实际调用的是重载方法
  11. }

而这个重载service的默认实现就是根据method的值来分别调用【doGet】,【doPost】,【doPut】,【doDelete】等

  1. protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  2. String method = req.getMethod();
  3. long lastModified;
  4. if (method.equals("GET")) {
  5. lastModified = this.getLastModified(req);
  6. if (lastModified == -1L) {
  7. this.doGet(req, resp);
  8. } else {
  9. long ifModifiedSince;
  10. try {
  11. ifModifiedSince = req.getDateHeader("If-Modified-Since");
  12. } catch (IllegalArgumentException var9) {
  13. ifModifiedSince = -1L;
  14. }
  15. if (ifModifiedSince < lastModified / 1000L * 1000L) {
  16. this.maybeSetLastModified(resp, lastModified);
  17. this.doGet(req, resp);
  18. } else {
  19. resp.setStatus(304);
  20. }
  21. }
  22. } else if (method.equals("HEAD")) {
  23. lastModified = this.getLastModified(req);
  24. this.maybeSetLastModified(resp, lastModified);
  25. this.doHead(req, resp);
  26. } else if (method.equals("POST")) {
  27. this.doPost(req, resp);
  28. } else if (method.equals("PUT")) {
  29. this.doPut(req, resp);
  30. } else if (method.equals("DELETE")) {
  31. this.doDelete(req, resp);
  32. } else if (method.equals("OPTIONS")) {
  33. this.doOptions(req, resp);
  34. } else if (method.equals("TRACE")) {
  35. this.doTrace(req, resp);
  36. } else {
  37. String errMsg = lStrings.getString("http.method_not_implemented");
  38. Object[] errArgs = new Object[]{method};
  39. errMsg = MessageFormat.format(errMsg, errArgs);
  40. resp.sendError(501, errMsg);
  41. }
  42. }

所以实际开发时,可以重写整个重载后的service方法,也可以根据你实际情况重写相应的【doGet】,【doPost】,【doPut】,【doDelete】等

2.4 ServletConfig接口

ServletConfig接口的签名

public interface ServletConfig {
    String getServletName();    //获取Servlet的名称

    ServletContext getServletContext();

    String getInitParameter(String var1);    //获取Servlet初始化的参数

    Enumeration<String> getInitParameterNames();    //获取初始化参数名称列表
}

2.5 ServletContext接口

ServletContext是Servlet与Servlet容器之间直接通信的接口。Servlet容器在启动一个Web应用时,会为他创建一个ServletContext对象。每个Web应用都有唯一的ServletContext对象,可以把ServletContext对象形象的理解为Web应用的总管家,同样的一个Web应用中的所有Servlet对象都共享一个总管家,Servlet对象们可以通过这个总管家来访问容器中的各种资源。

ServletContext对象的几个常用方法:

方法 描述
String getInitParameter(String var1);
Enumeration getInitParameterNames();
获取Web应用的初始化参数或参数名列表
getRealPath(String path) 根据参数的指定的虚拟路径,返回文件系统中的真实路径
getContextPath() 获取Web应用的URL入口
getResourceAsStream(String path) 返回一个用于读取文件的输入流,参数path为web应用根目录
setAttribute(String name, Object value) 存储一个值到ServletContext中
getAttribute(String name) 从ServleteContext中获取值

2.6 HttpServletRequest接口

HttpServletRequest代表客户端的请求。它是ServletRequest的子接口,当Servlet容器接收到客户端要求访问特定Servlet请求时,容器先解析客户端的原始请求数据,把他封装成一个ServletRequest对象。如果客户端是浏览器,则默认是Http协议,则会封装成为HttpServletRequest对象。

HttpServletRequest常用方法

方法 说明
getParameter(String name)
getParameterValues(String name)
根据请求的参数名,获取请求的参数值
Enumeration getParameterNames(); 返回请求中所有的参数名称列表
getParameterMap(); 获取请求中所有的参数键值对
getRequestURL() 获取请求路径
getRequestURI() URL除去域名或者ip和端口部分,剩下的就是URI
getMethod() 获取请求方式
getServletPath() 获取请求的Servlet名称

2.7 HttpServlteResponse接口

当容器接收到请求时,除了封装一个ServletRequest对象外,还会生成一个默认的ServlteResponse对象,将两个对象一起传递给service方法,在service方法中可以对客户端的响应做相应的修改已达到开发的目的。
同样ServletResponse接口也有一个特定的子接口是HttpServlteResponse。

HttpServlteResponse接口的常用

getWriter() 获取一个PrintWriter对象直接写入内容到响应的正文中去
setContentType() 设置响应类型
setRedirect() 请求重定向

3. 会话机制

3.1 cookie

Cookie的英文愿意是”点心”它是客户端访问Web服务器时,服务器在客户端硬盘上存放的信息,好像是服务器送给给客户的”点心”。 服务器可以根据Cookie来跟踪客户状态,这对于需要区别客户的场合特别有用。

Cookie的运行机制时由Http协议规定的,多数Web服务器和浏览器都支持Cookie:

  1. HTTP响应结果中添加Cookie数据。
  2. 解析HTTP请求中的Cookie数据。

而绝大多数浏览器为了支持Cookie,具备了以下功能

  1. 解析HTTP相应结果中的Cookie数据,并保存到本地磁盘
  2. 读取本地磁盘上的Cookie数据,并把它添加到HTTP请求中

image.png
以上行为均是http协议会话机制的默认行为
当然也可以人工使用代码进行干预:

1. 在服务器端在cookies中新增一个cookie

Cookie cookie = new Cookie("username", "马云");
resp.addCookie(cookie);

2. 在服务器获取cookie中的值

Cookie[] cookies = req.getCookies();
Arrays.stream(cookies)
     .forEach((cookie -> System.out.println("cookie name:" + cookie.getName() + ",cookie value:" +cookie.getValue())));

3. setMaxAge方法

setMaxAge的方法来设置Cookie的有效期。参数expiry以秒为单位,它具有如下含义:

  • 当expiry大于0,就指示浏览器在客户端硬盘上保存Cookie的时间为expiry秒
  • 当expiry等于0,就指示浏览器删除当前的Cookie。
  • 当expiry小于0,就指示浏览器不要把Cookie保存到客户端磁盘。仅仅知识将该cookie存在于浏览器进程中,当浏览器关闭进程时,Cookie也就消失了。

浏览器默认的有效期为-1。

4. getMaxAge方法

读取coookie的有效期

课堂代码.: 写一个CookieServlet类,doGet()方法中,

  1. 先读取客户端所有cookie,把每个Cookie的名字,值,和有效期都写回给客户端,
  2. 向客户端新增一个cookie, maxAge默认
  3. 连续访问CookieServlet类之后,启动另一个不同的浏览器访问,观察是否有cookie

5. 设置path

Cookie cookie = new Cookie("username", "马云");
cookie.setPath("/servlet/member/");
resp.addCookie(cookie);

如此设置一下, 则该cookie只对servlet工程,member模块前缀的servlet可见

3.2HttpSession

3.1 什么是HttpSession

session机制采用的是在服务器端保持 HTTP 状态信息的方案 。由于HTTP协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是Session机制

当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否包含了一个session标识(即sessionId),如果已经包含一个sessionId则说明以前已经为此客户创建过session,服务器就按照session id把这个session检索出来使用(如果检索不到,可能会新建一个,这种情况可能出现在服务端已经删除了该用户对应的session对象)。如果客户请求不包含sessionId,则为此客户创建一个session并且生成一个与此session相关联的sessionId,这个session id将在本次响应中返回给客户端保存。
image.png

3.2 HttpSession的生命周期

HttpSession在用户第一次访问Web应用中支持会话的任意一个程序,或者,web应用在session销毁后用户访问Web应用的程序时,tomcat会为新的访问创建一个会话。

在用户关闭浏览器或者到达指定时间之后,会话会被销毁。

ssession默认存活时间为30分钟

3.3 HttpSession常用的API

  1. getID()
  2. invalidate(): 销毁当前会话
  3. setAttribute(Streing name, Object value)
  4. getAttribute(String name)
  5. getAttributeNames()
  6. removeAttribute(String name)
  7. isNew()
  8. setMaxInactiveInterval()
  9. getMaxInactiveInterval()
  10. getServletContext()

4. 转发和包含

Servlet对象由Servlet容器创建,并且Servlet对象的Service()方法也有容器调用。一个Servlet对象可否直接调用另一个Servlet对象的Service()方法呢? 答案是否定的,因为一个Servlet对象无法获取另一个Servlet对象的引用。

但是Web应用在相应客户端的一个请求时,很有可能业务逻辑非常复杂并且代码量庞大,需要多个Web组件共同协作,才能生成响应结果,一个Servlet对象无法直接调用另一个Servlet对象的service()方法,但是Servlet规范为Web组件之间的协作提供了两种途径:

  • 请求转发:Servlet(原组件)先对客户请求做了一些预处理操作,然后把请求转发给其他Web组件(目标组件)来完成包括生成响应结果在内的后续操作
  • 包含:Servlet(原组件)把其他Web组件(目标组件)生成的响应结果包含到自身的响应结果中

转发和包含有几个共同特点:

  • 原组件和目标组件至始至终都共享同一个ServletRequest和ServletResponse对象
  • 目标组件都可以是Servlet,JSP或者HTML文档
  • 都依赖javax.servlet.RequesDispatcher接口

RequesDisDipatcher接口源码中只包含了两个方法:

    void forward(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    void include(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

在调用他们时,都要将当前ServletRequest对象和ServletReponse对象当作参数经行传递

而获取RequesDisDipatcher接口实例的方式有两个

  1. ServletContext的getRequestDispatcher(String path)方法,参数path只能是目标组件的绝对路径
  2. ServletRequest的getRequestDispatcher(String path) 方法,参数既可以是绝对路径,也可以是相对路径

绝对路径就是相对于web目录根路径

4.1请求转发

关键步骤:

RequestDispatcher dispatcher = req.getRequestDispatcher("output");    //1.获取转发的实例

dispatcher.forward(req, resp);        //2. 转发操作


使用forword方法有几点要注意

  • 由于forward()方法先清空用于存放响应正文数据的缓冲区,因此Servlet原组件生成的响应结果不会被发送到客户端,只有目标组件生成的响应结果生成的响应结果才会被发送到客户端
  • 如果源文件在请求转发之前,已经提交了响应结果(例如调用了flushBuffer()方法,或者close方法,那么forward()方法会抛出异常ILLegalStateException异常。

4.2 包含

include方法可以将数个目标组件的响应结果都包含在自己的响应结果中
关键代码:

RequestDispatcher source1 = req.getRequestDispatcher("source1");
RequestDispatcher source2 = req.getRequestDispatcher("source1");

source1.include(req, resp);    
source2.include(req, resp);

它的处理逻辑如下:

  • 如果目标组件为Servlet或者JSP,就调用他们的响应的service()方法,把该方法产生的响应正文添加到原组件的响应结果中,如果目标组件为HTML文档,就直接把文档的内容添加到原组件的响应结果中。
  • 返回源组件的服务方法中,继续执行后续代码块

原组件与被包含的目标组件的输出数据都会被添加到响应结果中
目标组件中对响应状态代码或者响应头所做的修改都会被忽略

5.重定向

Http协议规定了一种重定向机制,它的基本机制内容如下

  1. 用户在浏览器输入忒的那个URL,请求访问服务器端的某个组件
  2. 服务器端的组件返回一个状态代码为302的响应结果,该响应结果的含义规定:让浏览器端再请求访问另一个Web组件,响应结果中附带提供了另一个Web组件的URL,另一个Web组件有可能再同一个Web服务器上,也有可能再另一个Web服务器上
  3. 浏览器接收到这种响应结果后,再立即自动请求访问另一个web组件
  4. 浏览器端接受到来自另一个Web组件的响应结果

ServletAPI中HttpServletResponse接口的sendRedirect(String location)方法用于重定向,location必须是绝对路径或者外部网址。