3.4指令

指令是**JSP语法元素的第一种类型。它们指示JSP转换器如何翻译JSP页面为ServletJSP2.2定义了多个指令,但只有pageinclude指令最重要**,本章会详细讨论这两个指令。taglibtagattribute以及variable这些指令将在其他章节介绍。

3.3 隐式对象

概述

Servlet容器会传递几个对象给它运行的Servlet。例如,可以通过Servletservice方法拿到HttpServletRequestHttpServletResponse对象,以及可以通过init方法访问到ServletConfig对象。此外,可以通过调用HttpServletRequest对象的getSession方法拿到HttpSession对象。

JSP隐式对象

JSP中,可以通过使用隐式对象来访问上述对象。JSP隐式对象如下表所示。

对象 类型
request javax.servlet.http.HttpServletRequest
response javax.servlet.http.HttpServletResponse
out javax.servlet.jsp.JspWriter
session javax.servlet.http.HttpSession
application javax.servlet.ServletContext
config javax.servlet.ServletConfig
pageContext javax.servlet.jsp.PageContext
page javax.servlet.jsp.HttpJspPage
exception java.lang.Throwable

request隐式对象

该隐式对象代表Servlet/JSP容器传递给Servlet服务方法的HttpServletRequest对象。可以将request理解为一个指向HttpServletRequest对象的引用变量。下面的代码示例,从HttpServletRequest对象中返回userName参数值:

1
2
3
<%
String userName = request.getParameter("userName");
%>

pageContext隐式对象

获取Servlet相关对象

pageContext提供了有用的上下文信息,并且通过其方法可以访问各种Servlet相关对象,如getRequestgetResponsegetServletContextgetServletConfiggetSession。当然,这些方法在脚本中不是非常有用的,因为可以更直接地通过隐式对象requestresponsesessionapplication等来直接访问。

获取和设置属性

PageContext中提供了另一组有趣的方法:用于获取和设置属性的方法,也就是getAttribute方法和setAttribute方法。属性值可被存储在4个范围之一:页面请求会话应用程序

  • 页面范围是最小范围,这里存储的属性只在同一个JSP页面之中可用。
  • 请求范围是指属性存储在当前的ServletRequest里面。
  • 会话范围指属性存储在当前的HttpSession里面。
  • 应用程序范围指属性存储在应用的ServletContext里面。

setAttribute方法

PageContextsetAttribute方法签名如下:

1
2
public abstract void setAttribute(java.lang.String name,
java.lang.Object value, int scope)

其中,scope的取值范围为PageContext对象的中存储静态int值:PAGE_SCOPEREQUEST_ SCOPESESSION_SCOPEAPPLICATION_SCOPE

把属性存储到页面范围

若要保存一个属性到页面范围,可以直接使用setAttribute重载方法:

1
2
public abstract void setAttribute(java.lang.String name, 
java.lang.Object value)

实例

如下脚本将一个属性保存到ServletRequest中:

1
2
3
4
5
<%
//product is a Java object
pageContext.setAttribute("product", product,
PageContext.REQUEST_SCOPE);
%>

同样效果的Java代码如下:

1
2
3
<%
request.setAttribute("product", product);
%>

out隐式对象

隐式对象out引用了一个javax.servlet.jsp.JspWriter对象,这类似于你在调用HttpServletResponsegetWriter方法时得到的java.io.PrintWriter对象。可以通过调用它的print方法将消息发送到浏览器。例如:

1
out.println("Welcome");

Demo

implicitObjects.jsp页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<%@page import="java.util.Enumeration"%>
<html>
<head>
<title>JSP Implicit Objects</title>
</head>
<body>
<b>Http headers:</b>
<br />
<%
for (Enumeration<String> e = request.getHeaderNames(); e.hasMoreElements();)
{
String header = e.nextElement();
out.println(header + ": " + request.getHeader(header) + "<br/>");
}
%>
<hr />
<%
out.println("Buffer size: " + response.getBufferSize() + "<br/>");
out.println("Session id: " + session.getId() + "<br/>");
out.println("Servlet name: " + config.getServletName() + "<br/>");
out.println("Server info: " + application.getServerInfo());
%>
</body>
</html>

浏览器显示效果如下:
这里有一张图片
在浏览器中具体看到的内容,取决于所使用的浏览器及其环境。
注意,在默认情况下,JSP编译器会将JSP页面的内容类型设为text/html。如果要使用不同的类型,则需要
通过调用response.setContentType()或者使用页面指令来设置内容类型。例如,下面就是将内容类型设置为text/json

1
response.setContentType("text/json");

3.2 注释

JSP页面中的两种注释

在开发中为JSP页面添加注释是一个良好的习惯。JSP支持两种不同的注释格式:
(1)JSP注释。该注释记录了在JSP页面中做了什么。JSP注释不会发送到客户端.
(2)HTML/XHTML注释。这些注释将会发送到浏览器上.

JSP注释

JSP注释以“<%--”开始,以“--%>”结束。下面是一个例子:

1
<%-- JSP注释 --%>

注意 JSP注释不会被发送到浏览器端,也不可以嵌套。

HTML注释

HTML/XHTML注释语法如下:

1
<!-- HTML注释 -->

一个HTML/XHTML注释不会被容器处理,会原样发送给浏览器。HTML/XHTML注释的一个用途是用来确定JSP页面本身。

1
<!-- 这个是 /jsp/store/displayProducts.jsp 的输出 -->

尤其是在运行有多个JSP片段的应用时,会特别有用。开发人员可以根据HTML源代码中的HTML注释,找出是哪一个JSP页面或片段产生了这些相应的HTML片段。

3.1 JSP概述

JSP相较Servlet的优点

**JSP页面本质上是一个Servlet**。然而,用JSP页面开发比使用Servlet更容易,主要有两个原因。

  • 首先,不必编译JSP页面;
  • 其次,JSP页面是一个以.jsp为扩展名的文本文件,可以使用任何文本编辑器来编写它们。

JSP在哪里运行

JSP页面在JSP容器中运行,一个Servlet容器通常也是JSP容器。例如,Tomcat就是一个Servlet/JSP容器。

在JSP第一次被请求时 容器要做什么

当一个JSP页面第一次被请求时,Servlet/JSP容器主要做以下两件事情:
(1)把JSP页面转换成JSP页面实现类,该实现类是一个实现javax.servlet.jsp.JspPage接口或其子接口javax.servlet.jsp.HttpJspPage的Java类。而javax.servlet.jsp.JspPage接口又是javax.servlet.Servlet的子接口,这使得**每一个JSP页面都是一个Servlet**。该实现类的类名由Servlet/JSP容器生成。如果出现转换错误,则相关错误信息将被发送到客户端。
(2)如果转换成功,Servlet/JSP容器随后编译该Servlet类,并装载和实例化该类,并像其他正常的Servlet一样执行生命周期操作。

在JSP页面的后续请求时 容器要做什么

对于同一个JSP页面的后续请求,Servlet/JSP容器会先检查JSP页面是否被修改过。

  • 如果该JSP页面被修改过,则该JSP页面会被重新转换为Servlet、然后编译并执行。
  • 如果该JSP页面没有被修改过,则执行已经在内存中的JSP Servlet

这样一来,一个JSP页面的第一次调用的实际花费总比后续调用的花费多,因为第一次调用涉及转换和编译。

如何减少第一次调用JSP页面时的花费

为了解决这个问题,可以执行下列动作之一:

  • 配置应用程序,使所有的JSP页面在应用程序启动时被调用,也就是在引用程序启动时就转换和编译,而不是在第一次请求时被调用。
  • 预编译JSP页面,并将其部署为Servlet

JSP自带的API包

JSP自带的API包含4个包,如下所示:

  • javax.servlet.jsp。这个包包含了用于Servlet/JSP容器将JSP页面翻译为Servlet的核心类和接口。其中的两个重要成员是JspPageHttpJspPage接口。所有的JSP页面实现类必须实现JspPageHttpJspPage接口。在HTTP环境下,实现HttpJspPage接口是显而易见的选择。
  • javax.servlet.jsp.tagext。这个包中包含了用于开发自定义标签的类型。
  • javax.el。这个包中提供了统一表达式语言的API
  • javax.servlet.jsp.el。这个包提供了一组必须由Servlet/JSP容器支持,以便在JSP页面中使用表达式语言的类。

除了javax.servlet.jsp.tagext,我们很少直接使用JSP API。事实上,编写JSP页面时,我们更关心Servlet API,而非JSP API。当然,我们还需要掌握JSP语法,本章后续会进一步说明。开发JSP容器或JSP编译器时,JSP API已被广泛使用。
可以在以下网址查看JSP API:
https://docs.oracle.com/javaee/7/api/index.html?javax/servlet/jsp/package-summary.html

模板数据和语法元素

JSP页面可以包含模板数据和语法元素。这里,语法元素是一些具有特殊意义的JSP转换符。例如,“<%”是一个元素,因为它表示在JSP页面中的Java代码块的开始。“%>”也是一个元素,因为它是Java代码块的结束符。除去语法元素外的一切是模板数据模板数据会原样发送给浏览器。例如,**JSP页面中的HTML标记和文字都是模板数据**。

Demo

第一个JSP页面

下面是一个名为welcome.jsp的JSP页面。这个页面仅仅向用户发送一个Welcome。

1
2
3
4
5
6
<html>
<head><title>Welcome</title></head>
<body>
Welcome
</body>
</html>

在Tomcat中,welcome.jsp页面在第一次请求时被翻译成名为welcome_jsp的Servlet。你可以在Tomcat工作目录下的子目录中找到生成的Servlet,该Servlet继承
自org.apache.jasper. runtime.HttpJspBase抽象类,并实现了javax.servlet.jsp.HttpJspPage接口,org.apache.jasper. runtime.HttpJspBase继承于javax.servlet.http.HttpServlet类。
下面是为welcome.jsp生成的Servlet。

JSP页面对应的Servlet

JSP页面会被编译成Servlet,并发布到Tomcat安装目录下的\work\Catalina\localhost\项目名\包名\类名_jsp.java,我这里是D:\dev\apache-tomcat-8.5.35\work\Catalina\localhost\app03a\org\apache\jsp\welcome_jsp.java,打开这个文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/*
* Generated by the Jasper component of Apache Tomcat
* Version: Apache Tomcat/8.5.35
* Generated at: 2019-03-26 08:24:51 UTC
* Note: The last modified time of this file was set to
* the last modified time of the source file after
* generation to assist with modification tracking.
*/
package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
public final class welcome_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent,
org.apache.jasper.runtime.JspSourceImports {
private static final javax.servlet.jsp.JspFactory _jspxFactory =
javax.servlet.jsp.JspFactory.getDefaultFactory();
private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants;
private static final java.util.Set<java.lang.String> _jspx_imports_packages;
private static final java.util.Set<java.lang.String> _jspx_imports_classes;
static {
_jspx_imports_packages = new java.util.HashSet<>();
_jspx_imports_packages.add("javax.servlet");
_jspx_imports_packages.add("javax.servlet.http");
_jspx_imports_packages.add("javax.servlet.jsp");
_jspx_imports_classes = null;
}
private volatile javax.el.ExpressionFactory _el_expressionfactory;
private volatile org.apache.tomcat.InstanceManager _jsp_instancemanager;
public java.util.Map<java.lang.String,java.lang.Long> getDependants() {
return _jspx_dependants;
}
public java.util.Set<java.lang.String> getPackageImports() {
return _jspx_imports_packages;
}
public java.util.Set<java.lang.String> getClassImports() {
return _jspx_imports_classes;
}
public javax.el.ExpressionFactory _jsp_getExpressionFactory() {
if (_el_expressionfactory == null) {
synchronized (this) {
if (_el_expressionfactory == null) {
_el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
}
}
}
return _el_expressionfactory;
}
public org.apache.tomcat.InstanceManager _jsp_getInstanceManager() {
if (_jsp_instancemanager == null) {
synchronized (this) {
if (_jsp_instancemanager == null) {
_jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig());
}
}
}
return _jsp_instancemanager;
}
public void _jspInit() {
}
public void _jspDestroy() {
}
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
final java.lang.String _jspx_method = request.getMethod();
if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method) && !javax.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) {
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSPs only permit GET POST or HEAD");
return;
}
final javax.servlet.jsp.PageContext pageContext;
javax.servlet.http.HttpSession session = null;
final javax.servlet.ServletContext application;
final javax.servlet.ServletConfig config;
javax.servlet.jsp.JspWriter out = null;
final java.lang.Object page = this;
javax.servlet.jsp.JspWriter _jspx_out = null;
javax.servlet.jsp.PageContext _jspx_page_context = null;
try {
response.setContentType("text/html");
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write("<html>\r\n");
out.write("<head><title>Welcome</title></head>\r\n");
out.write("<body>\r\n");
out.write("Welcome\r\n");
out.write("</body>\r\n");
out.write("</html>");
} catch (java.lang.Throwable t) {
if (!(t instanceof javax.servlet.jsp.SkipPageException)){
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
try {
if (response.isCommitted()) {
out.flush();
} else {
out.clearBuffer();
}
} catch (java.io.IOException e) {}
if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
else throw new ServletException(t);
}
} finally {
_jspxFactory.releasePageContext(_jspx_page_context);
}
}
}

JSP实现类的主体方法

正如我们在上面的代码中看到的,JSP页面的主体是_jspService方法,该方法中响应输出如下:

1
2
3
4
5
6
7
8
9
10
11
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
......
out.write("<html>\r\n");
out.write("<head><title>Welcome</title></head>\r\n");
out.write("<body>\r\n");
out.write("Welcome\r\n");
out.write("</body>\r\n");
out.write("</html>");
......
}

这个_jspService方法被定义在HttpJspPage,并被HttpJspBaseservice方法调用。HttpJspBase类中的service方法如下所示:

1
2
3
4
5
public final void service(HttpServletRequest request,
HttpServletResponse response) throws ServletException,
IOException {
_jspService(request, response);
}

一个JSP页面不同于一个Servlet的另一个方面是,JSP页面不需要添加注解或在部署描述符中配置来映射URL。在应用程序目录中的每一个JSP页面可以直接在浏览器中输入路径页面访问。

app03a应用程序的结构非常简单,由一个空的WEB-INF目录和welcome.jsp页面构成,如下图所示:
这里有一张图片
可以通过如下URL访问welcome.jsp页面:
http://localhost:8080/app03a/welcome.jsp

说明

添加新的JSP界面后,无须重启Tomcat

显示今日日期

todaysDate.jsp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@page import="java.util.Date"%>
<%@page import="java.text.DateFormat"%>
<html>
<head><title>显示当前日期</title></head>
<body>
<%
DateFormat dateFormat =
DateFormat.getDateInstance(DateFormat.LONG);
String s = dateFormat.format(new Date());
out.println("当前日期: " + s);
%>
</body>
</html>

上面的JSP页面最终编译成:
E:\apache-tomcat-8.5.35\work\Catalina\localhost\app03a\org\apache\jsp\todaysDate_jsp.java
这个Servlet,查看这个Servlet,可以看到_jspService方法中的响应输出部分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
......
out.write("<html>\r\n");
out.write("<head><title>显示当前日期</title></head>\r\n");
out.write("<body>\r\n");
DateFormat dateFormat =
DateFormat.getDateInstance(DateFormat.LONG);
String s = dateFormat.format(new Date());
out.println("当前日期: " + s);
out.write("\r\n");
out.write("</body>\r\n");
out.write("</html>");
......
}

现在可以通过如下URL访问todaysDate.jsp页面:
http://localhost:8080/app03a/todaysDate.jsp
显示效果如下:
这里有一张图片
todaysDate.jsp页面发送了几个HTML标签和字符串"当前日期: "以及今天的日期到浏览器。
请注意两件事情。首先,Java代码可以出现在JSP页面中的任何位置,并通过<%%>包括起来,这个<%...%>块被称为scriplet。其次,可以使用page指令的import属性导入在JSP页面中使用的Java类型,如果没有导入的类型,必须在代码中写Java类的全路径名称。

第3章 Java Server Pages(JSP)概述

Servlet的缺点

Servlet有两个缺点是无法克服的:

  • 写在Servlet中的所有HTML标签必须写成Java字符串的形式,这使得处理HTTP响应报文的工作变得十分烦琐;
  • 所有的文本和HTML标记是硬编码,导致即使是表现层的微小变化,如改变背景颜色,也需要重新编译Servlet

JSP

Java Server Pages(JSP)解决了上述两个问题。不过,JSP不会取代Servlet,相反,它们具有互补性。现代的Java Web应用会同时使用ServletJSP页面。

本章概述

本章介绍了JSP技术,并讨论了在JSP页面中,隐式对象的使用意见,3个语法元素(指令、脚本元素和动作),还讨论了错误处理。可以用标准语法或XML语法编写JSP。用XML语法编写的JSP页面被称为JSP文档。由于很少用XML语法编写JSP,故本章不做介绍。在本章中,我们将主要学习JSP标准语法。

2.4 Java Servlet 会话管理 通过HttpSession对象

在所有的会话跟踪技术中,HttpSession 对象是最强大和最通用的。一个用户可以有且最多有一个HttpSession,并且不会被其他用户访问到。

谁创建HttpSession对象

HttpSession对象在用户第一次访问网站的时候自动被创建。

如何获取HttpSession对象

你可以通过调用HttpServletRequestgetSession方法获取该对象。getSession有两个重载方法:

1
2
HttpSession getSession()
HttpSession getSession(boolean create)

没有参数的getSession方法会返回当前的HttpSession,若当前没有,则创建一个并返回。
getSession(false)返回当前HttpSession,如当前不存在,则返回null
getSession(true)返回当前HttpSession,若当前没有,则创建一个并返回,getSession(true)getSession()的效果一致.

如何往HttpSession中存入数据

可以通过HttpSessionsetAttribute方法将值放入HttpSession,该方法的方法签名如下:

1
void setAttribute(java.lang.String name, java.lang.Object value)

请注意,不同于URL重新、隐藏域或cookie放入到HttpSession 的值,是存储在内存中的,因此,不要往HttpSession放入太多对象或放入大对象。
尽管现代的Servlet容器在内存不够用的时候会将保存在HttpSessions的对象转储到二级存储上,但这样有性能问题,因此小心存储。

HttpSession中的数据要满足的条件

此外,放到HttpSession的值不限于String类型,可以是任意实现java.io.Serializablejava对象,因为在内存不够用的时候,Servlet容器会将这些对象放入文件或数据库中,当然你也可以将不支持序列化的对象放入HttpSession,只是这样,当Servlet容器进行序列化的时候会失败并报错。
调用setAttribute方法时,若传入的name参数此前已经使用过,则会用新值覆盖旧值。

从HttpSession对象中取出数据

通过调用HttpSessiongetAttribute方法可以取回之前放入HttpSession中的对象,该方法的签名如下:

1
java.lang.Object getAttribute(java.lang.String name)

从HttpSession对象中批量取出数据

HttpSession还有一个非常有用的方法,名为getAttributeNames,该方法会返回一个Enumeration对象来迭代访问保存在HttpSession中的所有值:

1
java.util.Enumeration<java.lang.String> getAttributeNames()

注意,所有保存在HttpSession的数据不会被发送到客户端,不同于其他会话管理技术,**Servlet容器为每个HttpSession生成唯一的标识,并将该标识发送给浏览器**,或创建一个名为JSESSIONIDcookie,或者在URL后附加一个名为jsessionid 的参数。在后续的请求中,浏览器会将标识提交给服务端,这样服务器就可以识别该请求是由哪个用户发起的。Servlet容器会自动选择一种方式传递会话标识,无须开发人员介入。
提示

  • 隐藏域技术会往浏览器发送带隐藏域的表单.
  • Cookie技术会往浏览器发送Cookie.

获取Http对象的标识

可以通过调用 HttpSessiongetId方法来读取该标识:

1
java.lang.String getId()

此外,HttpSession.还定义了一个名为invalidate 的方法。该方法强制会话过期,并清空其保存的对象。默认情况下,HttpSession 会在用户不活动一段时间后自动过期,该时间可以通过部署描述符的 session-timeout元素配置,若设置为30,则会话对象会在用户最后一次访问30分钟后过期,如果部署描述符没有配置,则该值取决于Servlet容器的设定。
大部分情况下,你应该主动销毁无用的HttpSession,以便释放相应的内存。

查看设置超时时间

可以通过调用HttpSessiongetMaxInactiveInterval方法来查看会话多久会过期。该方法返回一个数字类型,单位为秒。调用setMaxInactiveInterval 方法来单独对某个HttpSession 设定其超时时间:

1
void setMaxInactiveInterval(int seconds)

若设置为0,则该HttpSession 永不过期。通常这不是一个好的设计,因为该 HttpSession 所占用的堆内存将永不释放,直到应用重加载或Servlet容器关闭。

Demo

一个简单的购物Demo,具有显示商品列表,商品详情页面,购物车页面.并且在商品详情页面提供添加到购物车的功能.

Product

商品类用来存储商品的信息,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package session.management.httpsession;
//一个商品的信息.
public class Product
{
// 商品编号.
private int id;
// 商品名称.
private String name;
// 商品详细描述.
private String description;
// 商品价钱.
private float price;
public Product( int id,String name,String description,float price)
{
this.id = id;
this.name = name;
this.description = description;
this.price = price;
}
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getDescription()
{
return description;
}
public void setDescription(String description)
{
this.description = description;
}
public float getPrice()
{
return price;
}
public void setPrice(float price)
{
this.price = price;
}
}

购物车中光有商品而已是不行的,还要知道用户买了几个商品,商品和对应的数量构成了购物车中记录的数据结构。

ShoppingItem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package session.management.httpsession;
//一次购物元素,对应于购物车中的一个商品.
public class ShoppingItem
{
// 商品
private Product product;
// 数量
private int quantity;
public ShoppingItem(Product product,int quantity)
{
this.product = product;
this.quantity = quantity;
}
public Product getProduct()
{
return product;
}
public void setProduct(Product product)
{
this.product = product;
}
public int getQuantity()
{
return quantity;
}
public void setQuantity(int quantity)
{
this.quantity = quantity;
}
}

有了商品,有了记录,下面就来写功能代码了.

ShoppingCartServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
package session.management.httpsession;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet(
name = "ShoppingCartServlet",
urlPatterns =
{"/products", "/viewProductDetails", "/addToCart", "/viewCart"}
)
public class ShoppingCartServlet extends HttpServlet
{
private static final long serialVersionUID = -20L;
//
private static final String CART_ATTRIBUTE = "cart";
private static final String tableStyle = "<style>table{border-right:1px solid #F00;border-bottom:1px solid #F00}\r\n"
+ "table td{border-left:1px solid #F00;border-top:1px solid #F00}</style>";
// 商品列表
private List<Product> products = new ArrayList<Product>();
private NumberFormat currencyFormat = NumberFormat
.getCurrencyInstance(Locale.CHINA);
@Override
public void init() throws ServletException
{
products.add(new Product(1, "苹果", "新鲜上市的苹果", 5.5F));
products.add(new Product(2, "香蕉", "新鲜上市的香蕉", 6.5F));
products.add(new Product(3, "菠萝", "新鲜上市的菠萝", 4.8F));
products.add(new Product(4, "草莓", "新鲜上市的草莓", 12.5F));
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
response.setCharacterEncoding("utf-8");
String uri = request.getRequestURI();
if (uri.endsWith("/products")) {
sendProductList(response);
} else if (uri.endsWith("/viewProductDetails")) {
sendProductDetails(request, response);
} else if (uri.endsWith("/viewCart")) {
showCart(request, response);
}
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
response.setCharacterEncoding("utf-8");
// add to cart
int productId = 0;
int quantity = 0;
try {
// 从参数中取出id属性的值
productId = Integer.parseInt(request.getParameter("id"));
// 取出数量属性的值
quantity = Integer.parseInt(request.getParameter("quantity"));
} catch (NumberFormatException e) {}
// 根据Id从商品列表中找出该商品
Product product = getProduct(productId);
if (product != null && quantity >= 0) {
// 创建一个购物记录
ShoppingItem shoppingItem = new ShoppingItem(product, quantity);
// 取得session对象
HttpSession session = request.getSession();
@SuppressWarnings(
"unchecked"
)
// 从session对象中,取出(查找)名称为`CART_ATTRIBUTE`的`List<ShoppingItem>`对象
List<ShoppingItem> cart = (List<ShoppingItem>) session
.getAttribute(CART_ATTRIBUTE);
// 如果查找不到,这说明session对象中还没有这个对象.
if (cart == null) {
// 创建对象
cart = new ArrayList<ShoppingItem>();
// 添加到session对象中
session.setAttribute(CART_ATTRIBUTE, cart);
}
// 添加一个商品到购物车中
cart.add(shoppingItem);
}
sendProductList(response);
}
private void sendProductList(HttpServletResponse response)
throws IOException
{
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.println("<html><head><title>商品列表</title>" + tableStyle
+ "</head><body><h2>商品列表</h2>");
writer.println("<ul>");
for (Product product : products) {
writer.println("<li>" + product.getName() + "("
+ currencyFormat.format(product.getPrice()) + ") ("
+ "<a href='viewProductDetails?id=" + product.getId()
+ "'>宝贝详情</a>)");
}
writer.println("</ul>");
writer.println("<a href='viewCart'>查看购物车</a>");
writer.println("</body></html>");
}
// 根据商品ID,从商品列表中查找一个商品.
private Product getProduct(int productId)
{
for (Product product : products) {
if (product.getId() == productId) {
return product;
}
}
return null;
}
// 显示商品列表
private void sendProductDetails(HttpServletRequest request,
HttpServletResponse response) throws IOException
{
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
int productId = 0;
try {
// 读取商品编号
productId = Integer.parseInt(request.getParameter("id"));
} catch (NumberFormatException e) {}
// 根据编号查找商品对象
Product product = getProduct(productId);
if (product != null) {
writer.println("<html><head>" + "<title>商品详情</title>" + tableStyle
+ "</head>" + "<body><h2>商品详情</h2>"
+ "<form method='post' action='addToCart'>");
writer.println("<input type='hidden' name='id' " + "value='"
+ productId + "'/>");
writer.println("<table>");
writer.println(
"<tr><td>商品名称:</td><td>" + product.getName() + "</td></tr>");
writer.println("<tr><td>商品描述:</td><td>" + product.getDescription()
+ "</td></tr>");
writer.println(
"<tr><td>购买数量:</td><td><input name='quantity'/>斤</td></tr>"
+ "<tr><td colspan='2'><input type='submit' value='加入购物车'/>"
+ "</td>" + "</tr>");
writer.println("<tr><td colspan='2'>"
+ "<a href='products'>返回商品列表</a>" + "</td></tr>");
writer.println("</table>");
writer.println("</form></body></html>");
} else {
writer.println("<h2>找不到该商品</h2>");
}
}
// 显示购物车
private void showCart(HttpServletRequest request,
HttpServletResponse response) throws IOException
{
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer
.println("<html><head><title>购物车</title>" + tableStyle + "</head>");
writer.println("<body><a href='products'>" + "返回商品列表</a>");
HttpSession session = request.getSession();
@SuppressWarnings(
"unchecked"
)
List<ShoppingItem> cart = (List<ShoppingItem>) session
.getAttribute(CART_ATTRIBUTE);
if (cart != null) {
writer.println("<table>");
writer.println("<tr><td style='width:150px'>数量" + "</td>"
+ "<td style='width:150px'>商品名称</td>"
+ "<td style='width:150px'>单品价格</td>" + "<td>合计</td></tr>");
// 总价格
double total = 0.0;
// 遍历购物车ArrayList,取出一次购物项,
for (ShoppingItem shoppingItem : cart) {
// 从购物项中,取出一个商品对象.
Product product = shoppingItem.getProduct();
// 从购物项中,取出该商品的数量
int quantity = shoppingItem.getQuantity();
if (quantity != 0) {
float price = product.getPrice();
writer.println("<tr>");
writer.println("<td>" + quantity + "</td>");
writer.println("<td>" + product.getName() + "</td>");
writer.println(
"<td>" + currencyFormat.format(price) + "</td>");
double subtotal = price * quantity;
writer.println(
"<td>" + currencyFormat.format(subtotal) + "</td>");
total += subtotal;
writer.println("</tr>");
}
}
writer.println("<tr><td colspan='4' " + "style='text-align:right'>"
+ "总价格:" + currencyFormat.format(total) + "</td></tr>");
writer.println("</table>");
}
writer.println("</table></body></html>");
}
}

代码详解

创建并初始化商品列表

1
2
3
4
5
6
7
8
9
10
// 商品列表
private List<Product> products = new ArrayList<Product>();
@Override
public void init() throws ServletException
{
products.add(new Product(1, "苹果", "新鲜上市的苹果", 5.5F));
products.add(new Product(2, "香蕉", "新鲜上市的香蕉", 6.5F));
products.add(new Product(3, "菠萝", "新鲜上市的菠萝", 4.8F));
products.add(new Product(4, "草莓", "新鲜上市的草莓", 12.5F));
}

从商品列表中查找出一个商品

1
2
3
4
5
6
7
8
9
10
// 根据商品ID,从商品列表中查找一个商品.
private Product getProduct(int productId)
{
for (Product product : products) {
if (product.getId() == productId) {
return product;
}
}
return null;
}

匹配URL

1
2
3
4
5
@WebServlet(
name = "ShoppingCartServlet",
urlPatterns =
{"/products", "/viewProductDetails", "/addToCart", "/viewCart"}
)

doGet方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
response.setCharacterEncoding("utf-8");
String uri = request.getRequestURI();
//显示产品列表
if (uri.endsWith("/products")) {
sendProductList(response);
}
//显示产品详情
else if (uri.endsWith("/viewProductDetails")) {
sendProductDetails(request, response);
}
//显示购物车
else if (uri.endsWith("/viewCart")) {
showCart(request, response);
}
}

显示产品列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void sendProductList(HttpServletResponse response)
throws IOException
{
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.println("<html><head><title>商品列表</title>" + tableStyle
+ "</head><body><h2>商品列表</h2>");
writer.println("<ul>");
for (Product product : products) {
writer.println("<li>" + product.getName() + "("
+ currencyFormat.format(product.getPrice()) + ") ("
+ "<a href='viewProductDetails?id=" + product.getId()
+ "'>宝贝详情</a>)");
}
writer.println("</ul>");
writer.println("<a href='viewCart'>查看购物车</a>");
writer.println("</body></html>");
}

显示商品详情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 显示商品详情
private void sendProductDetails(HttpServletRequest request,
HttpServletResponse response) throws IOException
{
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
int productId = 0;
try {
// 读取商品编号
productId = Integer.parseInt(request.getParameter("id"));
} catch (NumberFormatException e) {}
// 根据编号查找商品对象
Product product = getProduct(productId);
if (product != null) {
writer.println("<html><head>" + "<title>商品详情</title>" + tableStyle
+ "</head>" + "<body><h2>商品详情</h2>"
+ "<form method='post' action='addToCart'>");
writer.println("<input type='hidden' name='id' " + "value='"
+ productId + "'/>");
writer.println("<table>");
writer.println(
"<tr><td>商品名称:</td><td>" + product.getName() + "</td></tr>");
writer.println("<tr><td>商品描述:</td><td>" + product.getDescription()
+ "</td></tr>");
writer.println(
"<tr><td>购买数量:</td><td><input name='quantity'/>斤</td></tr>"
+ "<tr><td colspan='2'><input type='submit' value='加入购物车'/>"
+ "</td>" + "</tr>");
writer.println("<tr><td colspan='2'>"
+ "<a href='products'>返回商品列表</a>" + "</td></tr>");
writer.println("</table>");
writer.println("</form></body></html>");
} else {
writer.println("<h2>找不到该商品</h2>");
}
}

显示商品详情页,表单属性如下:

1
<form method='post' action='addToCart'>

当用户点击加入购物车时,会使用POST方法把表单发送给.\addToCart这个URL.

doPost方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
response.setCharacterEncoding("utf-8");
// add to cart
int productId = 0;
int quantity = 0;
try {
// 从参数中取出id属性的值
productId = Integer.parseInt(request.getParameter("id"));
// 取出数量属性的值
quantity = Integer.parseInt(request.getParameter("quantity"));
} catch (NumberFormatException e) {}
// 根据Id从商品列表中找出该商品
Product product = getProduct(productId);
if (product != null && quantity >= 0) {
// 创建一个购物记录
ShoppingItem shoppingItem = new ShoppingItem(product, quantity);
// 取得session对象
HttpSession session = request.getSession();
@SuppressWarnings(
"unchecked"
)
// 从session对象中,取出(查找)名称为`CART_ATTRIBUTE`的`List<ShoppingItem>`对象
List<ShoppingItem> cart = (List<ShoppingItem>) session
.getAttribute(CART_ATTRIBUTE);
// 如果查找不到,这说明session对象中还没有这个对象.
if (cart == null) {
// 创建对象
cart = new ArrayList<ShoppingItem>();
// 添加到session对象中
session.setAttribute(CART_ATTRIBUTE, cart);
}
// 添加一个商品到购物车中
cart.add(shoppingItem);
}
sendProductList(response);
}

显示购物车

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 显示购物车
private void showCart(HttpServletRequest request,
HttpServletResponse response) throws IOException
{
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer
.println("<html><head><title>购物车</title>" + tableStyle + "</head>");
writer.println("<body><a href='products'>" + "返回商品列表</a>");
HttpSession session = request.getSession();
@SuppressWarnings(
"unchecked"
)
List<ShoppingItem> cart = (List<ShoppingItem>) session
.getAttribute(CART_ATTRIBUTE);
if (cart != null) {
writer.println("<table>");
writer.println("<tr><td style='width:150px'>数量" + "</td>"
+ "<td style='width:150px'>商品名称</td>"
+ "<td style='width:150px'>单品价格</td>" + "<td>合计</td></tr>");
// 总价格
double total = 0.0;
// 遍历购物车ArrayList,取出一次购物项,
for (ShoppingItem shoppingItem : cart) {
// 从购物项中,取出一个商品对象.
Product product = shoppingItem.getProduct();
// 从购物项中,取出该商品的数量
int quantity = shoppingItem.getQuantity();
if (quantity != 0) {
float price = product.getPrice();
writer.println("<tr>");
writer.println("<td>" + quantity + "</td>");
writer.println("<td>" + product.getName() + "</td>");
writer.println(
"<td>" + currencyFormat.format(price) + "</td>");
double subtotal = price * quantity;
writer.println(
"<td>" + currencyFormat.format(subtotal) + "</td>");
total += subtotal;
writer.println("</tr>");
}
}
writer.println("<tr><td colspan='4' " + "style='text-align:right'>"
+ "总价格:" + currencyFormat.format(total) + "</td></tr>");
writer.println("</table>");
}
writer.println("</table></body></html>");
}

2.3 Cookies

URL重写隐藏域仅适合保存无须跨越太多页面的信息。如果需要在多个页面间传递信息,则以上两种技术实现成本高昂,因为你不得不在每个页面都进行相应处理。幸运的是,Cookies技术可以帮助我们。
Cookies是一个很少的信息片段,可自动地在浏览器和Web服务器间交互,因此cookies可存储在多个页面间传递的信息。Cookie作为HTTP header的一部分,其传输由HTTP协议控制。此外,你可以控制cookies的有效时间。浏览器通常支持每个网站高达20个cookies
Cookies的问题在于用户可以通过改变其浏览器设置来拒绝接受cookies
要使用cookies,需要熟悉javax.servlet.http.Cookie 类以及HttpServletRequestHttpServletResponse两个接口。

创建Cookies

可以通过传递namevalue两个参数给Cookie 类的构造函数来创建一个cookies

1
Cookie cookie = new Cookie(name, value);

如下是一个创建语言选择的cookie示例:

1
Cookie languageSelectionCookie = new Cookie("language", "Italian");

Cookie属性

创建完一个Cookie对象后,你可以设置domainpathmaxAge属性。其中,maxAge 属性决定cookie何时过期

服务器如何发送Cookie

要将cookie发送到浏览器,需要调用 HttpServletResponseadd方法:

1
httpServletResponse.addCookie(cookie);

浏览器如何发送Cookie

浏览器在访问同一Web服务器时,会将之前收到的 cookie一并发送。 此外,Cookies也可以通过客户端的javascript脚本创建和删除。

服务器端如何读取浏览器提交的Cookie

服务端若要读取浏览器提交的cookie,可以通过 HttpServletRequest接口的getCookies方法,该方法返回一个Cookie数组,若没有Cookies则返回null.

查找Cookie

你需要遍历整个数组来查询某个特定名称的cookie。如下为查询名为maxRecordscookie的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//获取浏览器提交的所有Cookie数组
Cookie[] cookies = request.getCookies();
//引用,用来保存你想要得到的某个cookie对象
Cookie maxRecordsCookie = null;
if (cookies != null) {
for (Cookie cookie : cookies)
{
//查找名称为maxRecords的cookie对象
if (cookie.getName().equals("maxRecords"))
{
//保存找到的这个cookie对象
maxRecordsCookie = cookie;
break;
}
}
}

目前,还没有类似于getCookieByName这样的方法来帮助简化工作。

删除Cookie

没有一个直接的方法来删除一个cookie,你只能创建一个同名的Cookie,并将maxAge属性设置为0,并添加到HttpServletResponse接口中,覆盖掉原来的Cookie即可。如下为删除一个名为userNamecookie代码:

1
2
3
Cookie cookie = new Cookie("userName", "");
cookie.setMaxAge(0);
response.addCookie(cookie);

demo

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package session.management.cookies;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
public class CookieTools
{
/**
* 从浏览器提交的Cookie中找到名称为name
* 的Cookie对象.
*
* @param request
* 请求.
* @param name
* 要查找的Cookie的名称.
* @return Cookie对象,如果找到该对象,
* 则返回该对象,没找到则返回null.
*/
public static Cookie findCookieByName(
HttpServletRequest request,String name)
{
Cookie cookies[] = request.getCookies();
for (Cookie cookie : cookies) {
if (cookie.getName().equals(name)) {
return cookie;
}
}
return null;
}
}

设置Cookie的Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package session.management.cookies;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(
name = "SetCookieByForm",
urlPatterns =
{"/setcookie"}
)
public class SetCookieByForm extends HttpServlet
{
private static final long serialVersionUID = 1L;
// 设置一个菜单栏
public static final String MENU =
"<div style='background:#e8e8e8;"
+ "padding:15px'>"
+ "<a href='setcookie'>Set Cookie</a>&nbsp;&nbsp;"
+ "<a href='showfruits'>Show Fruits</a>&nbsp;&nbsp;"
+ "<a href='showcss'>Show CSS</a>&nbsp;&nbsp;"
+ "</div>";
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException
{
// 以UTF-8编码响应
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/html");
PrintWriter writer = resp.getWriter();
writer.print("<html>\r\n"
+ "<head>\r\n"
+ "<title>Preference</title>\r\n"
+ "</head>\r\n"
+ "<body>"
+ MENU
+ "<form method=\"post\">\r\n"
+ "<hr>\r\n"
+ "<table>\r\n"
+ "<tbody>\r\n"
+ "<tr>\r\n"
+ "<td>设置字体大小: </td>\r\n"
+ "<td>\r\n"
+ "<select name=\"cookieFontSize\">\r\n"
+ "<option>large</option>\r\n"
+ "<option>x-large</option>\r\n"
+ "<option>xx-large</option>\r\n"
+ "</select>\r\n"
+ "</td>\r\n"
+ "</tr>\r\n"
+ "</table><hr><table>\r\n"
+ "<tbody>\r\n"
+ "<tr>\r\n"
+ "<td>显示的水果数量:</td>\r\n"
+ "<td>\r\n"
+ "<select name=\"fruitNum\">\r\n");
for (int i = 0;
i < ShowFruitsByCookie.fruits.length;
i++)
{
writer.print(
"<option>" + (i + 1)
+ "</option>\r\n");
}
writer.print(
"</select>\r\n"
+ "</td>\r\n"
+ "</tr>\r\n"
+ "<tr>\r\n"
+ "<td rowspan=\"2\">\r\n"
+ "<input type=\"submit\" value=\"Set\">\r\n"
+ "</td>\r\n"
+ "</tr>\r\n"
+ "</tbody>\r\n"
+ "</table>\r\n"
+ "</form>\r\n"
+ "</body>\r\n"
+ "</html>");
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
// 以utf-8发送响应
response.setCharacterEncoding("utf-8");
// 读取表单提交的值
String fruitNum =
request.getParameter("fruitNum");
// 创建一个Cookie,设置为用户在表单中输入的值,
// 然后发送给浏览器
response.addCookie(
new Cookie("fruitNum", fruitNum));
// 获取表单提交的值
String cookieFontSize =
request.getParameter("cookieFontSize");
// 存储到Cookie中.
response.addCookie(
new Cookie("cookieFontSize", cookieFontSize));
// 响应post请求
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.println(
"<html><head>"
+ "<title>Preference</title>"
+ "</head><body>"
+ MENU
+ "<ul><li>你想要显示 <strong>"
+ fruitNum
+ "</strong> 种水果。</li>"
+ "<li>你想要设置CSS选择器<strong>"
+".cookieStyle</strong>的字体大小为:<strong>"
+ cookieFontSize
+ "</strong></li></ul></body></html>");
}
}

这个Servlet会把表单输入转换为Cookie发送到浏览器,对应的URL为http://localhost:8080/SessionManagement/setcookie,
当浏览器再次访问http://localhost:8080/SessionManagement/这个Java项目下的资源时,浏览器会把之前服务器发给他的Cookie,再原封不动的提交给服务器。这样服务器就可以根据Cookie来进行响应的操作。
在上面代码中,服务器分别发送fruitNumcookieFontSize这两个cookie给浏览器,下面将再创建两个Servlet来使用者连个Cookie。
填写表单,如下所示:
这里有一张图片
然后点击Set按钮,提交表单,得到提示如下:
这里有一张图片

根据Cookie显示数组中的元素

ShowFruitsByCookie这个Servlet会根据fruitNum这个Cookie的值,来显示打印数组中的元素,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package session.management.cookies;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(
name = "ShowFruitByCookie",
urlPatterns =
{"/showfruits"}
)
public class ShowFruitsByCookie extends HttpServlet
{
private static final long serialVersionUID =
7275643908439277071L;
static String[] fruits = {
"苹果", "葡萄", "芒果", "草莓",
"荔枝", "香蕉", "乌梅", "菠萝",
"樱桃", "枇杷", "芒果", "橙子",
"桔子", "柠檬", "柚子", "杨梅",
"木瓜", "桂圆", "榴莲", "西瓜",
"石榴", "山楂", "椰子", "山竹",
"橄榄", "柿子","李子", "桑葚",
"红枣", "柑橘", "话梅", "乌梅",
"红提", "桃子", "甜瓜", "香瓜",
"荸荠","龙眼", "沙果", "佛手",
"芭乐", "蓝莓", "西梅", "释迦",
"黄皮", "莲雾", "杏子", "槟榔",
"酸橙", "黑莓", "栗子", "板栗",
"金桔", "山梨", "毛桃", "白果",
"银杏", "青梅", "蜜桃","脐橙",
"沙枣", "凤梨", "椰枣", "油桃",
"鳄梨", "酸莓", "蛇果", "鸭梨"};
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
Cookie fruitNumCookie =
CookieTools.findCookieByName(request, "fruitNum");
if (fruitNumCookie != null) {
// 取出Cookie中的值.
String cookieValue = fruitNumCookie.getValue();
int fruitNum = Integer.parseInt(cookieValue);
if (fruitNum <= fruits.length) {
response.setCharacterEncoding("utf-8");
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.print(
"<html>\r\n"
+ "<head>\r\n"
+ "<title>showFruits</title>\r\n"
+ "</head>\r\n"
+ "<body>\r\n"
+ SetCookieByForm.MENU
+ "为你显示 <strong>"
+ fruitNum
+ "</strong> 种水果,如下所示:\r\n"
+ "<ul>");
for (int i = 0; i < fruitNum; i++) {
writer.print("<li>" + fruits[i] + "</li>");
}
writer.println("</ul>\r\n"
+ "</body>\r\n" + "</html>");
}
}
}
}

点击工具条上的Show Fruits 超链接,效果如下:
这里有一张图片

根据Cookie设置CSS

SetCSSByCookie这个Servlet根据cookieFontSize这个Cookie中的值来来设置元素的CSS样式,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package session.management.cookies;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(
name = "SetCSSByCookie",
urlPatterns =
{"/showcss"}
)
public class SetCSSByCookie extends HttpServlet
{
private static final long serialVersionUID =
-8190275536448562340L;
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
response.setCharacterEncoding("utf-8");
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
Cookie cookieFontSize =
CookieTools.findCookieByName(request,
"cookieFontSize");
writer.print(
"<html>\r\n"
+ "<head>\r\n"
+ "<title>根据Cookie设置CSS</title>\r\n"
+ "<style>");
writer.print(".cookieStyle{");
if (cookieFontSize != null) {
writer.print("font-size: "
+ cookieFontSize.getValue() + ";");
}
writer.print("}");
writer.println(
"</style>\r\n"
+ "</head>\r\n"
+ "<body>"
+ SetCookieByForm.MENU);
writer.println(
"<div class=\"cookieStyle\">这句话的样式通过Cookie设置.</div>");
writer.println("</body>\r\n"
+ "</html>");
}
}

点击工具条上的Show CSS超链接,显示效果如下:
这里有一张图片

URL重写

URL重写是一种会话跟踪技术,它将一个或多个键值对 添加到URL的查询字符串中,每个键值对 通常为 key=value形式,格式如下所示:

1
url?key_1=value_1&key_2=value_2...&key_n=value_n

注意,URL键值对之间用问号?来分割,键值对之间用单与符号&分割。
URL重写适合于在少数URL之间传递信息的情况下,它有如下限制:

  • URL在某些浏览器上最大长度为2000字符,所以URL重写可能无法传递大量数据。
  • 如果要传递信息到下一个资源,需要将信息插入到URL的查询字符串中,这种情况适用于动态页面之间传值,而不适用于静态页面之间传值;
  • URL重写需要在服务端上完成,所有的链接都必须带值,因此当一个页面存在很多链接时,其处理过程会是一个不小的挑战;
  • 对应信息中的某些字符,例如空格、单与符号&和问号?等必须用base64编码,而编码和解码会增加开销;
  • URL的内容会显示在浏览器的地址栏上,所以**URL重写不适合用来传递敏感信息**。

所以,**URL重写仅适合于在少量页面间 传递不敏感信息的情况。**
下面来写个Demo,使用URL重写在同一个页面中传递信息。

Demo

项目目录

创件一个名为SessionManagement的动态Java Web项目,然后创建项目结果如下所示:
这里有一张图片

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package session.management;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//配置Servlet映射到URL
@WebServlet(
name = "RewriteURL",
// 为一个servlet匹配多个URL
urlPatterns ={"/test"}
)
public class RewriteURL extends HttpServlet
{
private static final long serialVersionUID = 1L;
private ArrayList<String> fruits = null;
private ArrayList<String> vegetables = null;
// 初始化数据
@Override
public void init() throws ServletException
{
fruits = new ArrayList<String>(5);
fruits.add("苹果");
fruits.add("香蕉");
fruits.add("草莓");
fruits.add("西瓜");
fruits.add("龙眼");
vegetables = new ArrayList<String>(5);
vegetables.add("白菜");
vegetables.add("紫菜");
vegetables.add("菠菜");
vegetables.add("南瓜");
vegetables.add("冬瓜");
}
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
// 获取查询字符串
String choice = request.getParameter("choice");
//
if (choice == null) {
showIndex(response);
} else if ("fruits".equals(choice)) {
//如果用户选择了水果的显示水果
showFruits(response);
} else if ("vegetables".equals(choice)) {
//如果用户选择了蔬菜的显示蔬菜
showVegetables(response);
}
}
private void showIndex(HttpServletResponse response)
{
// 设置响应的格式为HTML
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
// 获取响应输出
PrintWriter writer;
try {
writer = response.getWriter();
String htmlResponse =
"<html>\r\n" +
"<head>\r\n"+
"<meta charset=\"utf-8\">\r\n"+
"<title>测试URL重写</title>\r\n"+
"</head>\r\n"+
"<body>\r\n"+
"<hr>\r\n" +
"<br><br>"+
"<hr>\r\n"+
"<a href=\"?choice=fruits\">显示水果</a>&nbsp;\r\n"+
"<a href=\"?choice=vegetables\">显示蔬菜</a>\r\n"+
"</body>\r\n" +
"</html>";
// 输出响应
writer.write(htmlResponse);
} catch (IOException e) {
e.printStackTrace();
}
}
private void showFruits(HttpServletResponse response)
{
// 设置响应的格式为HTML
response.setContentType("text/html");
//因为响应带有中文,设置编码格式为中文,
//不然会出现中文乱码??????这种情况。
response.setCharacterEncoding("UTF-8");
PrintWriter writer;
try {
// 获取响应输出
writer = response.getWriter();
// 生成水果列表
String ul = "";
for (String string : fruits) {
ul += "<li>" + string + "</li>";
}
//拼接成完整的HTML内容
String responseHTML =
"<html>\r\n" +
"<head>\r\n"+
"<meta charset=\"utf-8\">\r\n"+
"<title>测试URL重写</title>\r\n" +
"</head>\r\n"+
"<body>\r\n" +
"<a href=\"test\">返回首页</a>"+
"<hr>\r\n" + ul +
"<hr>\r\n"+
"<a href=\"?choice=fruits\">显示水果</a>&nbsp;\r\n"+
"<a href=\"?choice=vegetables\">显示蔬菜</a>\r\n"+
"</body>\r\n" +
"</html>";
// 输出响应对象中
writer.write(responseHTML);
//Servlet容器被把响应对象中的响应发给浏览器,我们不用管
} catch (IOException e) {
e.printStackTrace();
}
}
private void showVegetables(HttpServletResponse response)
{
// 设置响应的格式为HTML
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
// 获取响应输出
PrintWriter writer;
try {
writer = response.getWriter();
String ul = "";
for (String string : vegetables) {
ul += "<li>" + string + "</li>";
}
String responseHTML =
"<html>\r\n" +
"<head>\r\n"+
"<meta charset=\"utf-8\">\r\n"+
"<title>测试URL重写</title>\r\n" +
"</head>\r\n"+
"<body>\r\n" +
"<a href=\"test\">返回首页</a>"+
"<hr>\r\n" + ul +
"<hr>\r\n"+
"<a href=\"?choice=fruits\">显示水果</a>&nbsp;\r\n"+
"<a href=\"?choice=vegetables\">显示蔬菜</a>\r\n"+
"</body>\r\n" +
"</html>";
// 输出响应
writer.write(responseHTML);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

代码详解

URL映射

Servlet中的URL映射的注解如下所示:

1
2
3
4
5
6
//配置Servlet映射到URL
@WebServlet(
name = "RewriteURL",
// 为一个servlet匹配多个URL
urlPatterns ={"/test"}
)

name表示当前session.management.RewriteURL这个Servlet的部署名。
urlPatterns表示该ServletURL地址,这个是个数字形式,同一个Servlet可以设置不同的URL地址.
映射好URL后,后面就可以通过http://localhost:8080/项目名称/test这样的格式来访问这个Servlet,这里是:
http://localhost:8080/SessionManagement/test

初始化数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private ArrayList<String> fruits = null;
private ArrayList<String> vegetables = null;
// 初始化数据
@Override
public void init() throws ServletException
{
fruits = new ArrayList<String>(5);
fruits.add("苹果");
fruits.add("香蕉");
fruits.add("草莓");
fruits.add("西瓜");
fruits.add("龙眼");
vegetables = new ArrayList<String>(5);
vegetables.add("白菜");
vegetables.add("紫菜");
vegetables.add("菠菜");
vegetables.add("南瓜");
vegetables.add("冬瓜");
}

在这个Servlet中有两个成员变量fruitsvegetables用于存放要展示的数据。
当用户第一次通过URL访问到该Servelet时,Web容器(Tomcat)会调用init()方法来初始化数据。在这里,init()方法用来初始化成员变量的数据。

响应get请求 覆盖doGet方法

当直接通过http://localhost:8080/SessionManagement/test这个URL访问的时候,Tomcat会调用init()方法来初始化,初始化只进行一次.后面不再调用init()方法.然后调用doGet()方法来响应请求。doGet方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
// 获取查询字符串
String choice = request.getParameter("choice");
//
if (choice == null) {
showIndex(response);
} else if ("fruits".equals(choice)) {
//如果用户选择了水果的显示水果
showFruits(response);
} else if ("vegetables".equals(choice)) {
//如果用户选择了蔬菜的显示蔬菜
showVegetables(response);
}
}

doGet方法会获取请求中名称为choice的参数值。然后根据参数值的不同来调用不同的方法进行响应。
当我们请求http://localhost:8080/SessionManagement/test对应的Servlet时,URL中没有带查询字符串,所以不存在choice这个参数。request.getParameter("choice");返回null.这样就会调用showIndex(response);这个方法来响应。

返回初始界面

showIndex()方法如下,所谓的响应,就是往响应对象里写一些字符串。Web容器会把响应对象中的这些字符串处理一下然后发送给浏览器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private void showIndex(HttpServletResponse response)
{
// 设置响应的格式为HTML
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
// 获取响应输出
PrintWriter writer;
try {
writer = response.getWriter();
String htmlResponse =
"<html>\r\n" +
"<head>\r\n"+
"<meta charset=\"utf-8\">\r\n"+
"<title>测试URL重写</title>\r\n" +
"</head>\r\n"+
"<body>\r\n"+
"<hr>\r\n" +
"<br><br>"+
"<hr>\r\n"+
"<a href=\"?choice=fruits\">显示水果</a>&nbsp;\r\n"+
"<a href=\"?choice=vegetables\">显示蔬菜</a>\r\n"+
"</body>\r\n" +
"</html>";
// 输出响应
writer.write(htmlResponse);
} catch (IOException e) {
e.printStackTrace();
}
}

此时浏览器显示如下:
这里有一张图片
这个页面有两个超链接,如下所示:

1
2
<a href="?choice=fruits">显示水果</a>
<a href="?choice=vegetables">显示蔬菜</a>

响应重写的URL

在刚才的页面中,显示水果URL?choice=fruits这是个查询字符串,查询字符串中有一个名为choice的参数,参数值为fruits
如果点击这超链接,浏览器会再次发送一个HTTP请求给服务器。
这里URL中地址缺省,浏览器会默认发给当前的地址。也就是在http://localhost:8080/SessionManagement/test这个地址后面加上查询字符串,得到的URL为:http://localhost:8080/SessionManagement/test?choice=fruits,这相当于重写了URL.

虽然,这个时候请求的Servlet还是http://localhost:8080/SessionManagement/test这个URL对应的Servlet.但是这回跟第一次请求时的情况是不一样的,第一次没有带请求参数,这次带了请求参数。Tomcat还是会调用doGet来生成响应。
不过这次因为带了参数choice=fruits,doGet方法会调用showFruits方法来生成响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private void showFruits(HttpServletResponse response)
{
// 设置响应的格式为HTML
response.setContentType("text/html");
//因为响应带有中文,设置编码格式为中文,不然会出现中文乱码??????这种情况。
response.setCharacterEncoding("UTF-8");
PrintWriter writer;
try {
// 获取响应输出
writer = response.getWriter();
// 生成水果列表
String ul = "";
for (String string : fruits) {
ul += "<li>" + string + "</li>";
}
//拼接成完整的HTML内容
String responseHTML =
"<html>\r\n" +
"<head>\r\n"+
"<meta charset=\"utf-8\">\r\n"+
"<title>测试URL重写</title>\r\n" +
"</head>\r\n"+
"<body>\r\n" +
"<hr>\r\n" + ul +
"<hr>\r\n"+
"<a href=\"?choice=fruits\">显示水果</a>&nbsp;\r\n"+
"<a href=\"?choice=vegetables\">显示蔬菜</a>\r\n"+
"</body>\r\n" +
"</html>";
// 输出响应对象中
writer.write(responseHTML);
//Servlet容器被把响应对象中的响应发给浏览器,我们不用管
} catch (IOException e) {
e.printStackTrace();
}
}

这个方法会把水果列表中的数据输出。如下所示:
这里有一张图片
同理如果此时点击显示蔬菜链接我们就可以显示蔬菜。

第2章 会话管理

由于HTTP的无状态性,使得会话管理或会话跟踪成为Web应用开发一个无可避免的主题。默认下,一个Web服务器无法区分一个HTTP请求是第几次访问。而Web应用通常需要先登录后使用,例如,一个Web邮件应用通常要求用户登录后才能查看邮件。因此,当用户输入了相应的用户名和密码进行登录后,Web应用必须记住这个用户已经登录过了,在用户没有退出登录之前,Web应用不应该再次要求用户再次登录。换句话说,应用必须能管理用户的会话。

本章将阐述4种不同的状态保持技术:URL重写隐藏域cookiesHTTPSession对象。本章的示例代码为app02a

2.2 隐藏域

使用隐藏域来保持状态类似于URL重写技术,但不是将值附加到URL上,而是放到HTML表单的隐藏域中。当表单提交时,隐藏域的值也同时提交到服务器端。隐藏域技术仅当网页有表单时有效。该技术相对于 URL重写的优势在于:没有字符数限制,同时无须额外的编码。但该技术同URL重写一样,不适合跨越多个界面。
清单2.3展示了如何通过隐藏域来更新客户信息。清单2.2的Customer类为客户对象模型。

清单2.2 Customer类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package app02a.hiddenfields;
public class Customer
{
private int id;
private String name;
private String city;
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getCity()
{
return city;
}
public void setCity(String city)
{
this.city = city;
}
}

清单2.3 CustomerServlet类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package app02a.hiddenfields;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*
* Not thread-safe. For illustration purpose only
*/
@WebServlet(
name = "CustomerServlet",
urlPatterns =
{"/customer", "/editCustomer", "/updateCustomer"}
)
public class CustomerServlet extends HttpServlet
{
private static final long serialVersionUID = -20L;
private List<Customer> customers = new ArrayList<Customer>();
@Override
public void init() throws ServletException
{
Customer customer1 = new Customer();
customer1.setId(1);
customer1.setName("Donald D.");
customer1.setCity("Miami");
customers.add(customer1);
Customer customer2 = new Customer();
customer2.setId(2);
customer2.setName("Mickey M.");
customer2.setCity("Orlando");
customers.add(customer2);
}
private void sendCustomerList(HttpServletResponse response)
throws IOException
{
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.println("<html><head><title>Customers</title></head>"
+ "<body><h2>Customers </h2>");
writer.println("<ul>");
for (Customer customer : customers) {
writer.println("<li>" + customer.getName() + "("
+ customer.getCity() + ") (" + "<a href='editCustomer?id="
+ customer.getId() + "'>edit</a>)");
}
writer.println("</ul>");
writer.println("</body></html>");
}
private Customer getCustomer(int customerId)
{
for (Customer customer : customers) {
if (customer.getId() == customerId) {
return customer;
}
}
return null;
}
private void sendEditCustomerForm(HttpServletRequest request,
HttpServletResponse response) throws IOException
{
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
int customerId = 0;
try {
customerId = Integer.parseInt(request.getParameter("id"));
} catch (NumberFormatException e) {}
Customer customer = getCustomer(customerId);
if (customer != null) {
writer
.println("<html><head>" + "<title>Edit Customer</title></head>"
+ "<body><h2>Edit Customer</h2>" + "<form method='post' "
+ "action='updateCustomer'>");
writer.println(
"<input type='hidden' name='id' value='" + customerId + "'/>");
writer.println("<table>");
writer
.println("<tr><td>Name:</td><td>" + "<input name='name' value='"
+ customer.getName().replaceAll("'", "&#39;")
+ "'/></td></tr>");
writer
.println("<tr><td>City:</td><td>" + "<input name='city' value='"
+ customer.getCity().replaceAll("'", "&#39;")
+ "'/></td></tr>");
writer.println("<tr>" + "<td colspan='2' style='text-align:right'>"
+ "<input type='submit' value='Update'/></td>" + "</tr>");
writer.println("<tr><td colspan='2'>"
+ "<a href='customer'>Customer List</a>" + "</td></tr>");
writer.println("</table>");
writer.println("</form></body>");
} else {
writer.println("No customer found");
}
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
String uri = request.getRequestURI();
if (uri.endsWith("/customer")) {
sendCustomerList(response);
} else if (uri.endsWith("/editCustomer")) {
sendEditCustomerForm(request, response);
}
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
// update customer
int customerId = 0;
try {
customerId = Integer.parseInt(request.getParameter("id"));
} catch (NumberFormatException e) {}
Customer customer = getCustomer(customerId);
if (customer != null) {
customer.setName(request.getParameter("name"));
customer.setCity(request.getParameter("city"));
}
sendCustomerList(response);
}
}

CustomerServlet类继承自HttpServlet,其URL映射分别为/customer/editCustomer/updateCustomer。前两个URL会调用ServletdoGet方法, 而/updateCustomer 会调用doPost方法。
/customer是本例的入口URL。该URL会列举出在 init 方法中所初始化的类级别的列表对象customers(在 真实应用中,通常是从数据库中获取用户信息),如图下图所示。
这里有一张图片
如图上图所示,每个客户信息后都有一个edit链接, 每个edit链接的href属性为 /editCustomer?id=customerId。当通过/editCustomer访问servlet时, servlet会返回一个编辑表单,如下图所示。
这里有一张图片
此时,servlet返回的表单如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<form method="post" action="updateCustomer">
<input type="hidden" name="id" value="1">
<table>
<tbody>
<tr>
<td>Name:</td>
<td>
<input name="name" value="Donald D.">
</td>
</tr>
<tr>
<td>City:</td>
<td>
<input name="city" value="Miami">
</td>
</tr>
<tr>
<td colspan="2" style="text-align:right">
<input type="submit" value="Update">
</td>
</tr>
<tr>
<td colspan="2">
<a href="customer">Customer List</a>
</td>
</tr>
</tbody>
</table>
</form>

可以看到表达中的隐藏域为<input type="hidden" name="id" value="1">.

该隐藏域保存了所编辑的客户id,因为隐藏域包含在表单中,所以会随着表单一起提交给服务器,这样服务端就知道应更新哪个客户信息。

需要强调的是,表单是通过post方式提交的,因此调用的是servletdoPost方法。