jackdov.com Java Servlet 教程中文翻译

601 阅读23分钟

本文是我在学习 jackdov.com 的 Java Servlets 时的中文翻译。

因为感觉暂时用不到,所以 GZip Filter 章节没有翻译。

如果文章哪里有问题,请联系我,我会及时修改。

Java Servlet

Java Servlet 是 Java 的一种 web 技术。它是 Java 的第一个 Web 技术,此后许多新的 Web 技术也相继到来。不过,Java Servlet 还是非常有用的,既可以了解,也可以用于某些用例。

Java Servlet 是 Java 企业版(Java EE)的一部分。您需要在一个兼容 Servlet 的 "Servlet 容器"(如 Web 服务器)内运行您的 Java Servlet,以使其工作。

这篇关于 Java Servlet 的教程并不是要对 Java Servlet 进行详尽的描述。相反,它的目的是让您快速掌握 servlet,并教您最重要的概念。

Overview

在这篇文章中,我将试着给大家介绍一下 Java servlet。

什么是Servlet?

Servlet 是一个响应 HTTP 请求的 Java 对象。它运行在一个 Servlet 容器内。如下:

Java Servlet 容器内的 Servlet

Servlet 是 Java Web 应用程序的一部分。一个 Servlet 容器可以同时运行多个 Web 应用程序,每个容器内部都有多个 Servlet 运行。如下:

在一个 Java Servlet 容器内有多个 Servlet 的网络应用程序。

一个Java Web 应用程序可以包含 servlets 以外的其他组件。它还可以包含JSP、JSF、和Web Srevice。不过本教程只介绍 Servlet。

HTTP Request and Response(HTTP 请求和响应)

浏览器向 Java Web 服务器发送 HTTP 请求。Web 服务器检查该请求是否是一个 servlet。如果是,那么 servlet 容器就会传递该请求。然后,servlet 容器将找出请求哪个 servlet,并通过Servlet.service()方法来调用该 servlet。

一旦通过service()方法调用了servlet,servlet 就会处理请求,并生成一个相应。然后将响应发送回浏览器。

Servlet 容器

Java servlet 容器通常运行在 Java Web 服务器内。常见的几个知名的、免费的 Java Web 服务器有:

  1. Jetty
  2. Tomcat

Servlet Life Cycle(Servlet 的生命周期)

一个 servlet 遵循一定的生命周期。servlet 生命周期由 servlet 容器管理。生命周期包含以下步骤:

  1. 加载 Servlet 类。
  2. 创建 Servlet 的实例。
  3. 调用init()方法。
  4. 调用service()方法。
  5. 调用destroy()方法。

第 1、2、3 步只执行一次,即在最初加载 Servlet 的时候。默认情况下,servlet 不会被加载,直到收到对它的第一个请求。不过您可以在容器启动时强制容器加载 servlet 。请参阅web.xml Servlet 配置以了解更多有关的细节。

第4步会被执行多次--对servlet的每个HTTP请求都会执行一次。

第5步是在servlet容器卸载servlet时执行的。

下图将对每一步进行更详细的描述:

Java Servlet 的生命周期

Load Servlet Class(载入 Servlet 类)

在调用 servlet 之前,servlet 容器必须首先加载它的类定义。这就像加载任何其他类一样。

Create Instance of Servlet(创建Servlet实例)

当 servlet 类被加载时,servlet 容器会创建一个 servlet 的实例。

通常情况下,只创建一个 servlet 的实例,对 servlet 的并发请求会在同一个 servlet 实例上执行。不过这其实是由 servlet 容器来决定的,但通常情况下,只有一个实例。

Call the Servlets init() Method(调用Servlet init()方法)

当一个 servlet 实例被创建时,它的init()方法被调用。init()方法允许 servlet 在处理第一个请求之前初始化自己。

您可以在 web.xml 文件中为 servlet 指定 init 参数。

Call the Servlets service() Method(调用Servlet service()方法)

每接收到一个对 servlet 的请求,就会调用 servlet 的service()方法。对于HttpServlet子类,一般会调用doGet()doPost()等方法中的一个。

只要 servlet 容器中的 servlet 是存活的,就可以调用service()方法。因此,生命周期的这一步可以多次执行。

Call the Servlets destroy() Method(调用Servlet destroy()方法)

当一个 servlet 容器卸载一个 servlet 时,会调用它的destroy()方法。这个步骤只执行一次,因为一个 servlet 只被卸载一次。

如果容器关闭,或者容器在运行时重新加载整个 Web 应用程序,那么一个 servlet 就会被容器卸载。

Servlet Example(Servlet 例子)

一个 Servlet 只是一个实现了javax.servlet.Servlet的普通 Java 类。

实现这个接口最简单的方法是继承GenericServletHttpServlet类。

import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;


public class SimpleServlet extends GenericServlet {

  public void service(ServletRequest request, ServletResponse response)
        throws ServletException, IOException {

       // do something in here
  }
}

当一个对 Servlet 的 HTTP 请求发送到 Web 服务器, Web 服务器会调用你的 Servlet 的service()方法。

service()方法会读取请求,并生成响应发送到客户端(例如浏览器)。

下方是一个service()方法的例子:

public void service(ServletRequest request, ServletResponse response)
        throws ServletException, IOException {

  String yesOrNoParam = request.getParameter("param");

  if("yes".equals(yesOrNoParam) ){

      response.getWriter().write(
        "<html><body>You said yes!</body></html>");
  }

  if("no".equals(yesOrNoParam) ){
    
      response.getWriter().write(
        "<html><body>You said no!</body></html>");
  }
}

service()方法读取请求中的"param"参数,然后对比param参数对应的值是 "yes" 或者 "no",生成对应的响应发送给浏览器。

HttpServlet

javax.servlet.http.HttpServlet类是一个比上一章例子中所示的GenericServlet略高级的基类。

HttpServlet类读取 HTTP 请求,并判断该请求是否是 HTTP GET、POST、PUT、DELETE、HEAD 等 HTTP 方法,并调用其中一个对应的方法。

如果只响应 HTTP GET 请求,你可以扩展HttpServlet类,并且只覆盖doGet()方法。下面是一个例子:

public class SimpleHttpServlet extends HttpServlet {

  protected void doGet( HttpServletRequest request,
                        HttpServletResponse response)
        throws ServletException, IOException {

      response.getWriter().write("<html><body>GET response</body></html>");
  }
}

HttpServlet类有方法可以覆盖每个 HTTP 方法(GET,POST等)。下面是你可以重写的方法列表:

  • doGet()
  • doPost()
  • doHead()
  • doPut()
  • doDelete()
  • doOptions()
  • doTrace()

大多数情况下,你只想响应 HTTP GET 或 POST 请求,所以你只需覆盖这两个方法。

如果你想以同样的方式处理一个给定的 servlet 的 GET 和 POST 请求,你可以覆盖这两个方法,让一个方法调用另一个方法。下面是具体的实现:

public class SimpleHttpServlet extends HttpServlet {

  protected void doGet( HttpServletRequest request,
                        HttpServletResponse response)
        throws ServletException, IOException {

      doPost(request, response);
  }

  protected void doPost( HttpServletRequest request,
                         HttpServletResponse response)
        throws ServletException, IOException {

      response.getWriter().write("GET/POST response");
    }
}

我建议您尽可能使用HttpServlet而不是GenericServlet。 与GenericServlet相比,HttpServlet更易于使用,并且具有更多便捷方法。

HttpRequest

HttpServlet类的请求处理方法需要两个参数:

  1. javax.servlet.http.HttpRequest。
  2. javax.servlet.http.HttpResponse。

例如,这里是HttpServlet.doGet()方法的签名:

protected void doGet()
    HttpServletRequest请求。
    HttpServletResponse)
      throws ServletException, IOException {

}

在这篇文章中,我们将学习一下HttpRequest对象。

HttpRequest对象代表浏览器向你的 Web 应用程序发送的 HTTP 请求。因此,浏览器可能发送的任何内容,都可以通过HttpRequest来访问。

HttpRequest对象有很多方法,我在这里只介绍最常用的方法。其余的你可以在JavaDoc中阅读。

Parameters(参数)

请求参数是浏览器随请求一起发送的参数。请求参数通常作为 URL 的一部分(在 "query string" 中),或作为 HTTP 请求主体的一部分发送。例如:

http://jenkov.com/somePage.html?param1=hello¶m2=world

注意 URL 中的 "query string "部分。param1=hello¶m2=world这一部分包含两个参数值的参数:

param1=hello
param2=world

你可以像这样从HttpRequest对象中获取这些参数:

protected void doGet(
    HttpServletRequest请求。
    HttpServletResponse)
      throws ServletException, IOException {

    String param1 = request.getParameter("param1")。
        String param2 = request.getParameter("param2");

}

如果请求参数是在 HTTP 请求的 body 部分发送的,你可以使用同样的代码。如果给定名称的参数不存在,则返回null

一般来说,如果浏览器发送 HTTP GET 请求,参数会包含在 URL 的查询字符串中。如果浏览器发送 HTTP POST 请求,则参数包含在 HTTP 请求的 body 部分。

Headers(头)

请求头是浏览器随 HTTP 请求发送的键、值对。请求头包含了一些信息,例如,使用的是什么浏览器软件,浏览器能够接收什么文件类型等。简而言之,请求头包含了围绕着 HTTP 请求的很多元数据。

你可以像这样从HttpRequest对象中访问请求头:

String contentLength = request.getHeader("Content-Length");    

这个例子读取浏览器发送的Content-Length头。

如果浏览器发送的是 HTTP POST 请求,Content-Length头包含 HTTP 请求体中发送的字节数。如果浏览器发送 HTTP GET 请求,则不使用Content-Length头,上述代码将返回null

一般情况下,如果没有传递给getHeader()的名称的头存在,则返回null

InputStream

如果浏览器发送 HTTP POST 请求,请求参数和其他潜在的数据会在 HTTP 请求体中发送给服务器。在 HTTP 请求体中发送的不一定是请求参数,也可以是其它数据,它几乎可以是任何数据,比如一个文件或一个SOAP请求(Web服务请求)。

为了让你访问 HTTP POST 请求的请求体,你可以获得一个指向 HTTP 请求体的InputStream。下面是如何实现的:

InputStream requestBodyInput = request.getInputStream()。   

注意:你必须在调用任何getParameter()方法之前调用这个方法,因为在 HTTP POST 请求上调用getParameter()方法会导致 servlet 引擎解析 HTTP 请求体的参数。一旦解析完毕,你就不能再将 body 作为一个原始的字节流来访问。

你如何处理从InputStream中读取的数据是由你决定的。servlet 引擎不会帮你解析或解释这些数据。你只是得到它的原始数据。

Session(输入流)

也可以从HttpRequest对象中获取session对象。

session对象可以在不同的请求之间保存关于某个用户的信息。因此,如果你在一次请求中把一个对象设置到session对象中,那么在同一session内的任何后续请求中,它都可以被你读取。

下面是如何从HttpRequest对象中访问session对象:

HttpSession session = request.getSession();

关于session对象,我在这里就不多说了。它在后文会有更详细的介绍。

ServletContext(Servlet 上下文)

你也可以从HttpRequest对象中访问ServletContext对象。ServletContext包含了Web 应用的元信息。例如,你可以访问在 web.xml 文件中设置的上下文参数,你可以将请求转发给其他 servlet,你也可以在ServletContext中存储应用程序范围的参数。

下面是如何从HttpRequest对象中访问ServletContext对象:

ServletContext context = request.getSession().getServletContext()。       

正如你所看到的,你必须先得到session对象,才能获得对ServletContext对象。

关于ServletContext对象,我在这里就不多说了。它在后文会有更详细的介绍。

HttpResponse

HttpServlet类的请求处理方法需要两个参数:

  1. javax.servlet.http.HttpRequest。
  2. javax.servlet.http.HttpResponse。

例如,这是HttpServlet.doGet()方法的签名:

protected void doGet(
    HttpServletRequest request,
    HttpServletResponse response)
      throws ServletException, IOException {

}

在这篇文章中,我们将学习一下HttpResponse对象。

HttpResponse对象的目的是表示你的 Web 应用程序向浏览器发送回的 HTTP 响应,以响应浏览器向你的 Web 应用程序发出的 HTTP 请求。

HttpResponse对象有很多方法,所以我在这里只介绍最常用的方法。其余的方法你可以在 JavaDoc 中阅读。

Writing Html(写入 HTML)

要将 HTML 发送回浏览器,你必须从HttpResponse对象中获取PrintWriter。下面是具体操作方法:

PrintWriter writer = response.getWriter();

writer.write("<html><body>GET/POST response</body></html>")。

Headers(头)

就像请求对象一样,HttpResponse可以包含 HTTP 头信息。必须在任何数据被写入响应之前设置 Headers。你可以像这样在响应对象上设置一个头:

response.setHeader("Header-Name", "Header Value")。

正如你所看到的,响应头是一个键、值对。

Content-Type(内容类型)

Content-Type是一个响应头,它告诉浏览器你要发回的内容类型。例如,HTML 的内容类型是 text/html。同样,如果你发回给浏览器的是纯文本,你就使用内容类型text/lain

下面是如何在HttpResponse对象上设置Content-Type头:

response.setHeader("Content-Type", "text/html");

Writing Text(写入文本)

你可以把文本写回给浏览器,而不是 HTML,就像这样:

response.setHeader("Content-Type", "text/plain");

PrintWriter writer = response.getWriter();
writer.write("这只是纯文本")。

首先Content-Type头被设置为text/plain。然后,一个纯文本字符串被写入从响应对象中获得的writer

Content-Length(内容长度)

Content-Length头告诉浏览器你的 servlet 要发回多少个字节。如果你要发送二进制数据回来,你需要设置内容长度头。下面是如何设置的:

response.setHeader("Content-Length", "31642")。

Writing Binary Data(写入二进制数据)

你也可以把二进制数据写回浏览器,而不是文本。例如,你可以发送一个图像,一个 PDF 文件或一个 Flash 文件或类似的东西回去。

同样,你首先要将Content-Type头设置为与你要发送回来的数据相匹配的类型。例如,PNG 图像的内容类型是image/png

你可以在你最喜欢的搜索引擎中搜索 "mime types",找到一个mime类型(内容类型)的列表,这样你就可以找到你要发回的内容的mime类型。

为了将二进制数据写回浏览器,你不能使用从response.getWriter()获得的Writer。因为,Writer的是用于文本的写入方式。

相反,你必须使用从response.getOutputStream()方法获得的OutputStream。如下:

OutputStream outputStream = response.getOutputStream();

outputStream.write(...)。

Redirecting to a Different URL(重定向到不同的 URL)

您可以将浏览器重定向到与您的 servlet 不同的 URL。重定向时不能向浏览器发送任何数据。下面是你如何重定向:

response.sendRedirect("http://jenkov.com");

HttpSession

HttpSession对象代表一个用户会话。一个用户会话包含了用户在多个 HTTP 请求中的信息。

当用户第一次进入你的网站时,用户会被赋予一个唯一的 ID 来识别他的会话。这个 ID 通常存储在一个 cookie 或一个请求参数中。

下面是如何访问会话对象:

protected void doPost(HttpServletRequest request,
    HttpServletResponse response)
        throws ServletException, IOException {

    HttpSession session = request.getSession();
}

你可以将值存储在 session 对象中,然后再检索。首先,让我们看看如何在 session 对象中存储值:

session.setAttribute("userName", "theUserName");

这段代码设置了一个名为 "userName "的属性,其值为 "theUserName"。

要再次读取该值,你可以这样做。

String userName = (String) session.getAttribute("userName");

存储在 session 对象中的值被存储在 servlet 容器的内存中。

Sessions and Clusters(会话和集群)

如果你有一个集群中有 2 个 Web 服务器的架构,请记住,存储在一个服务器的 session 对象中的值,可能在另一个服务器的 session 对象中不可用。因此,如果用户的请求在两个服务器之间平均分配,有时会话值可能会丢失。

解决这个问题的方法是:

1.不要使用会话属性 2.使用会话数据库,将会话属性写入数据库,并从数据库中读取。 3.使用粘性会话,在整个会话中,用户总是被发送到同一个服务器。

RequestDisPatcher(请求分发器)

RequestDispatcher类使你的 servlet 能够从另一个 servlet 内部 "调用 "另一个 servlet,就好像一个 HTTP 请求是由浏览器向它发送的一样。

你可以从HttpServletRequest对象中获得一个RequestDispatcher,像这样:

protected void doPost(HttpServletRequest request,
                      HttpServletResponse)
        throws ServletException, IOException {

  RequestDispatcher requestDispatcher = 。
    request.getRequestDispatcher("/anotherURL.simple")。
}

上面的代码获取了一个针对任何被映射到 URL /anotherUrl.simple的 Servlet(或 JSP)的RequestDispatcher

你可以使用它的include()forward()方法调用RequestDispatcher

requestDispatcher.forward(request, response)。

requestDispatcher.include(request, response)。

通过调用include()forward()方法,servlet 容器调用了映射到RequestDispatcher的 URL 的任何 Servlet。

被激活的 servlet 可以访问与调用它的 servlet 相同的请求,并将写入与你当前 servlet 相同的HttpServletResponse对象。这样你就可以把 servlet 的输出合并成一个响应。

forward()include()方法有一点区别。

forward()方法的目的是用于转发请求,也就是说在调用 servlet 的响应被提交之后。使用该方法不能合并响应输出。

include()方法将调用 servlet 写的响应和激活的 servlet 合并。这样你就可以使用include()实现 "server side include"。

ServletContext

ServletContext是一个对象,它包含了关于你的 Web 应用程序的元信息。你可以通过HttpRequest对象访问它,像这样:

ServletContext context = request.getSession().getServletContext();

Context Attributes(上下文属性)

就像在 session 对象中一样,你可以在 servlet 上下文中存储属性。下面是如何操作的:

context.setAttribute("someValue", "aValue");

你可以像这样再次访问属性。

Object attribute = context.getAttribute("someValue")。

存储在ServletContext中的属性对你的应用程序中的所有 servlet 都是可用的,在请求和会话之间也是如此。这意味着,这些属性对 Web 应用的所有访问者都是可用的。会话属性只是对单个用户可用。

ServletContext属性仍然存储在 servlet 容器的内存中。这意味着,在服务器集群中,存在与会话属性相同的问题。

web.xml Servlet Configuration

要想从浏览器访问 Java servlet,你必须告诉 servlet 容器要部署哪些 servlet,以及要将 servlet 映射到哪些 URL。这个任务是在你的 Java Web 应用程序的 web.xml 文件中完成的。

Configuring and Mapping a Servlet(配置和映射一个Servlet)

要在 web.xml 文件中配置一个 servlet,你可以这样写:

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

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

<web-app>

  <servlet>
    <servlet-name>controlServlet</servlet-name>
    <servlet-class>com.jenkov.butterfly.ControlServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>controlServlet</servlet-name>
    <url-pattern>*.html</url-pattern>
  </servlet-mapping>
</web-app>    

首先你要配置 servlet。这是用<servlet>元素完成的。在这里,你给 servlet 取个名字,并写上 servlet 的类名。

其次,你将 servlet 映射到一个 URL 或 URL pattern。这是在<servlet-mapping>元素中完成的。在上面的例子中,所有以.html结尾的 URL 都被发送到 servlet。

其他可能的 servlet URL 映射有:

/myServlet

/myServlet.do

/myServlet*

*是一个通配符,意思是任何文本。正如你所看到的,你可以使用通配符(*)将一个 servlet 映射到一个单一的、特定的 URL,或者映射到一个 URL 的 pattern。你将使用什么取决于 servlet 的功能。

Servlet Init Parameters(Servlet初始化参数)

你可以从 web.xml 文件中向一个 servlet 传递参数。一个 servlet 的 init 参数只能由该 servlet 访问。下面是如何在 web.xml 文件中配置它们:

<servlet>
    <servlet-name>controlServlet</servlet-name>。
    <servlet-class>com.jenkov.butterfly.ControlServlet</servlet-class>。
    
        <init-param>
        <param-name>myParam</param-name>。
        <param-value>paramValue</param-value>。
        </init-param>
</servlet>

下面是如何从你的 servlet 内部读取 init 参数——在servlet init()方法中:

public class SimpleServlet extends GenericServlet {

  protected String myParam = null;

  public void init(ServletConfig servletConfig) throws ServletException{。
    this.myParam = servletConfig.getInitParameter("myParam")。
  }

  public void service(ServletRequest request, ServletResponse response)
        throws ServletException, IOException {

    response.getWriter().write("<html><body>myParam = " +。
            this.myParam + "</body></html>");
  }
}

当 servlet 容器第一次加载 servlet 时,会调用servlet init()方法。在 servlet 被加载,并且init()方法被成功调用之前,任何人都不能访问 servlet。

Servlet Load-on-Startup(提前启动Servlet)

<servlet>元素有一个名为<load-on-startup>的子元素,你可以用它来控制 servlet 容器何时应该加载 servlet。如果你没有指定<load-on-startup>元素,那么 servlet 容器通常会在第一个请求到达时加载你的 servlet。

通过设置一个<load-on-startup>元素,你可以告诉 servlet 容器在 servlet 容器启动时就加载 servlet。记住,当 servlet 被加载时,init()方法会被调用。

下面是一个配置的例子:

<servlet>
    <servlet-name>controlServlet</servlet-name>。
    <servlet-class>com.jenkov.webui.ControlServlet</servlet-class>。
    <init-param><param-name>container.script.static</param-name>。
                <param-value>/WEB-INF/container.script</param-value>。
    </init-param>
    <load-on-startup>1</load-on-startup>。
</servlet>

<load-on-startup>1</load-on-startup>元素里面的数字告诉 servlet 容器应该按照什么顺序加载 servlet。较低的数字先被加载。如果这个值是负数,或者没有指定,那么 servlet 容器可以在任何时候加载 servlet。

Context Parameters(上下文参数)

你也可以设置一些上下文参数,这些参数可以从你的应用程序中的所有 servlet 中读取。下面是如何配置一个上下文参数:

<context-param>
    <param-name>myParam</param-name>。
    <param-value>值</param-value>。
</context-param>

下面是如何从HttpServlet子类内部访问参数:

String myContextParam =
        request.getSession()
               .getServletContext()
               .getInitParameter("myParam")。

Cookies

HTTP Cookies 是网络应用程序可以在访问网络应用程序的用户的客户端机器上存储的小块数据。通常情况下,最多可以存储 4KB 的数据。本文将解释如何在 servlets(或 JSP)内部设置、读取和删除 Cookie。

示例

你可以像这样使用HttpServletResponse对象编写Cookie:

Cookie cookie = new Cookie("myCookie", "myCookieValue");

response.addCookie(cookie)。

正如你所看到的,cookie 由一个名称 "myCookie " 来标识,并有一个值 "myCookieValue" 。因此,你可以添加许多不同的标识(名称)的 cookie。这有点像一个 Hashtable。

每当浏览器访问网络应用时,它就会将存储在客户端机器上的 cookie 提交给网络应用。只有被访问的站点的 Cookie 才会被提交。来自其他站点的 Cookies 不会被提交。

Reading Cookies Sent From the Browser(读取浏览器发送的 Cookie)

你可以像这样通过HttpServletRequest读取Cookie:

Cookie[] cookies = request.getCookies()。

注意:getCookies()方法可能返回null!

现在你可以在 Cookie 的数组中迭代,找到你需要的 Cookie。不幸的是,没有办法获得一个具有特定名称的 cookie。再次找到该 cookie 的唯一方法是迭代 Cookie[] 数组并检查每个 cookie 名称。下面是一个例子:

Cookie[] cookies = request.getCookies();

String userId = null;
for(Cookie cookie : cookies){
    if("uid".equals(cookie.getName())) {
        userId = cookie.getValue()。
    }
}

这个例子找到了名为 "uid " 的 cookie,并将它的值存储在了userId中。

如果你需要访问多个 Cookie,你可以对 Cookie[] 数组进行一次迭代,然后将 Cookie 实例放入一个 Map 中,使用 Cookie 名称作为键,Cookie 实例作为值。如下:

Map cookieMap = new HashMap();
Cookie[] cookies = request.getCookies();

for(Cookie cookie : cookies){
    cookieMap.put(cookie.getName(), cookie)。
}

执行这段代码后,您现在可以使用 cookie 名称作为键(cookieMap.get("cookieName"))访问cookieMap中的 cookie。

Cookie Expiration(Cookie 过期时间)

一个重要的 Cookie 设置是 Cookie 的到期时间。这个时间告诉接收 cookie 的浏览器 cookie 应该保留多长时间。

你可以通过setMaxAge()方法来设置 cookie 的过期时间。该方法将 cookie 的生存秒数作为参数。如下:

Cookie cookie = new Cookie("uid", "123");

cookie.setMaxAge(24 * 60 * 60); // 24小时。

response.addCookie(cookie)。

这个例子首先创建了一个 Cookie 实例,名称为 "uid",值为 "123"。其次,它使用setMaxAge()方法将到期时间设置为 24 小时。最后,该示例在HttpServletResponse对象上设置了 cookie,因此 cookie 包含在发送给浏览器的响应中。

Removing Cookies(删除Cookie)

有时,您可能想从浏览器中删除一个 cookie。您可以通过设置 cookie 的到期时间来实现。您可以将过期时间设置为 0 或 -1。如果您将过期时间设置为 0,则会立即从浏览器中删除 cookie。如果您将过期时间设置为 -1,则 cookie 将在浏览器关闭时被删除。

下面是一个例子:

Cookie cookie = new Cookie("uid", "");

cookie.setMaxAge(0); 

response.addCookie(cookie)。

如果浏览器已经存储了一个名为 "uid " 的 cookie,那么在收到同名 ("uid")、过期时间为 0 的 cookie 后,该 cookie 将被删除。如果浏览器还没有存储该 cookie,那么这个新的 cookie 将被立即扔掉,因为它的过期时间为 0。

Additional Cookie Settings(其他 Cookie 设置)

一个 Cookie 除了过期外,还有各种其他的设置可以修改和访问。可以查看 Cookie 类 JavaDoc 了解更多细节。

Cooklie Use Case(Cookie使用案例)

Cookies 最常用来存储用户的特定信息,例如,一个独特的用户 ID(对于匿名用户,不登录),一个会话 ID,或用户特定的设置,你不希望存储在你的 Web 应用程序数据库。

Filter(过滤器)

Servlet 过滤器是一个可以拦截针对 Web 应用的 HTTP 请求的对象。

Servlet 过滤器可以拦截 servlet、JSP、HTML 文件或其他静态内容的请求,如下图所示:

Java网络应用中的Servlet过滤器

为了创建一个servlet过滤器,你必须实现javax.servlet.Filter接口。下面是一个servlet过滤器实现的例子:

import javax.servlet.*;
import java.io.IOException;

/**

 */
public class SimpleServletFilter implements Filter {

    public void init(FilterConfig filterConfig) throws ServletException {
    }
    
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain filterChain)
    throws IOException, ServletException {
    
    }
    
    public void destroy() {
    }
}

当 servlet 过滤器第一次被加载时,它的 init() 方法会被调用,就像 servlet 一样。

当一个 HTTP 请求发送你的 Web 应用,过滤器拦截了这个请求,过滤器可以检查请求 URI、请求参数和请求头,并根据这些决定是否要阻止或转发这个请求到目标 servlet、JSP 等。

做拦截的是doFilter()方法。下面是一个示例实现:

public void doFilter(ServletRequest request, ServletResponse response,
                     FilterChain filterChain)
throws IOException, ServletException {

    String myParam = request.getParameter("myParam")。
    
    if(!"blockTheRequest".equals(myParam)){。
        filterChain.doFilter(request, response);
    }
}

请注意doFilter()方法是如何检查请求参数myParam,看它是否等于字符串 "blockTheRequest"。如果不等于,则通过调用filterChain.doFilter()方法,将请求转发到请求的目标。如果这个方法没有被调用,则请求不会被转发,而只是被阻截。

如果请求参数myParam等于 "blockTheRequest",上面的 servlet 过滤器只是忽略请求。你也可以写一个不同的响应回给浏览器。只要使用ServletResponse对象就可以了,就像在 servlet 里面一样。

你可能需要将ServletResponse转换到HttpResponse,以从中获得PrintWriter。否则,你只能通过getOutputStream()来获得输出流。

下面是一个例子:

public void doFilter(ServletRequest request, ServletResponse response,
                     FilterChain filterChain)
throws IOException, ServletException {

    String myParam = request.getParameter("myParam")。
    
    if(!"blockTheRequest".equals(myParam)){。
        filterChain.doFilter(request,response)。
        return.doFilterChain()
    }
    
    HttpResponse httpResponse = (HttpResponse) httpResponse;
    httpResponse.getWriter().write("不同的响应......例如HTML")。
}

Configuring the Servlet Filter in web.xml(在web.xml中配置Servlet过滤器。)

你需要在 Web 应用程序的 web.xml 文件中配置 servlet 过滤器,然后才能工作。如下:

<filter>
    <filter-name>myFilter</filter-name>
    <filter-class>servlets.SimpleServletFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>myFilter</filter-name>
    <url-pattern>*.simple</url-pattern>
</filter-mapping>

在此配置下,所有 URL 以.simple结尾的请求都会被 servlet 过滤器拦截。其他的请求不会过滤。

Servlet Concurrenc(Servlet 并发)

一个 Java servlet 容器 / Web 服务器通常是多线程的。这意味着,对同一 servlet 的多个请求可能会在同一时间被执行。因此,你在实现 servlet 时需要考虑到并发性。

为了确保 servlet 是线程安全的,你必须遵循一些基本的经验法则:

  1. 你的service()方法不应该访问任何成员变量,除非这些成员变量本身是线程安全的。

  2. 你的service()不应该重新分配成员变量,因为这可能会影响在service()方法内部执行的其他线程。如果你真的,真的需要重新分配一个成员变量,请确保这是在一个同步块内完成的。

  3. 规则 1 和 2 也适用于静态变量。

  4. 本地变量永远是线程安全的。但请记住,局部变量指向的对象,可能不是这样的。如果这个对象是在方法内部实例化的,并且从未逃逸,那么就不会有问题。另一方面,一个局部变量指向某个共享对象,可能还是会引起问题。仅仅因为你把一个共享对象分配给一个本地引用,并不意味着这个对象自动成为线程安全的对象。

请求对象和响应对象当然是线程安全使用的。每向你的 servlet 发出一个请求,就会创建一个新的实例,因此,在你的 servlet 中执行的每一个线程都会创建一个新的实例。

下面是一张图,说明了上面提到的 servlet 并发规则 / 问题。红框代表你的 servlet 的service()方法应该小心访问的状态(变量)。

Other Shared Resources( 其他共享资源)

当然,不仅仅是 servlet 类本身内部的成员变量和静态变量,你需要小心访问。任何其他类中被你的 servlet 访问的静态变量,也必须是线程安全的。对于你的 servlet 访问的任何共享对象的成员变量也是如此。

Code Example(代码示例)

下面这段代码是对上述内容的实践:

public class SimpleHttpServlet extends HttpServlet {

  // Not thread safe, static.
  protected static List list = new ArrayList();

  // Not thread safe
  protected Map map = new HashMap();

  // Thread safe to access object, not thread safe to reassign variable.
  protected Map map = new ConcurrentHashMap();

  // Thread safe to access object (immutable), not thread safe to reassign variable.
  protected String aString = "a string value";


  protected void doGet( HttpServletRequest request,
                        HttpServletResponse response)
        throws ServletException, IOException {


    // Not thread safe, unless the singleton is 100% thread safe.
    SomeClass.getSomeStaticSingleton();


    // Thread safe, locally instantiated, and never escapes method.
    Set set = new HashSet();

  }
}