第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方法。

1.12 小结

Servlet技术是Java EE技术的一部分。所有Servlet都 运行在Servlet容器中,容器和Servlet间的接口为 javax.servlet.Servletjavax.servlet包还提供了一个名为GenericServletServlet实现类,该类是一个辅助类,以便可以方便的创建一个servlet。不过,大部分servlet都 运行在HTTP环境中,因此派生一个 javax.servlet.http.HttpServlet的子类更为有用,注意 HttpServlet也是GenericServlet的子类。

1.11 使用部署描述符

如在前面的例子中所见,编写和部署Servlet都是很容易的事情。部署的一个方面是用一个路径配置Servlet 的映射。在这些范例中,是利用WebServlet注解来将一个URL路径映射了一个Servlet,如下所示:

1
2
3
4
@WebServlet(
name = "FormServlet",
urlPatterns ={"/form"}
)

利用部署描述符是配置Servlet应用程序的另一种方法,部署描述符的详情将在第13章“部署描述符”中探讨。部署描述符总是命名为web.xml,并且放在WEB-INF目录下

程序示例

本章介绍了如何创建一个名为app01cServlet应用程序,并为它编写了一个web.xml
创建app01c项目然后在src目录下创建app01c包,app01c包中有SimpleServletWelcomeServlet这样两个Servlet,还有一个要映射Servlets的部署描述符web.xml

SimpleServlet类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package app01c;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class SimpleServlet extends HttpServlet
{
private static final long serialVersionUID = 8946L;
@Override
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException,IOException
{
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer
.print("<html><head></head>"
+ "<body>Simple Servlet</body></html");
}
}

WelcomeServlet类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package app01c;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class WelcomeServlet extends HttpServlet
{
private static final long serialVersionUID = 27126L;
@Override
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException,IOException
{
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.print(
"<html><head></head>" +
"<body>Welcome</body></html>");
}
}

部署描述文件web.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<display-name>app01c</display-name>
<servlet>
<!-- 部署名 -->
<servlet-name>SimpleServlet</servlet-name>
<!-- 类全名 -->
<servlet-class>app01c.SimpleServlet</servlet-class>
<load-on-startup>10</load-on-startup>
</servlet>
<servlet-mapping>
<!-- 部署名 -->
<servlet-name>SimpleServlet</servlet-name>
<!-- URL -->
<url-pattern>/simple</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>WelcomeServlet</servlet-name>
<servlet-class>app01c.WelcomeServlet</servlet-class>
<load-on-startup>20</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>WelcomeServlet</servlet-name>
<url-pattern>/welcome</url-pattern>
</servlet-mapping>
</web-app>

使用部署描述符的好处

使用部署描述符有诸多好处。

  • 其一,可以使用在@WebServlet中没有的元素,如load-on-startup 元素。使用这个元素可以在应用程序启动时就调用init方法来实例化Servlet,而不是在用户第一次访问该Servlet是时调用init方法来实例化Servlet。如果init方法很耗时的话,这样可以减少用户第一次访问时的等待时间。
  • 使用部署描述符的另一个好处是,如果需要修改配置值,如Servlet路径,则不需要重新编译Servlet

运行效果

现在,在部署描述符中声明SimpleServletWelcomeServlet,可以利用这些URL来访问它们:
http://localhost:8080/app01c/simple
http://localhost:8080/app01c/welcome

浏览器显示效果如下:
这里有一张图片
这里有一张图片

关于部署以及部署描述符的更多信息,请参考第13章。

1.10 处理HTML表单

一个Web应用程序中几乎总会包含一个或者多个HTML表单,供用户输入值。你可以轻松地将一个HTML表单从一个Servlet发送到浏览器。
当用户提交表单时,在表单元素中输入的值就会被当作请求参数发送到服务器。HTML输入域(文本域、隐藏域或者密码域)或者文本区的值,会被当作字符串发送到服务器。空的输入域或者文本区会发送空的字符串。因此,**凡是有输入域名称的,ServletRequest.getParameter方法绝对不会返回null**。

select元素

HTMLselect元素也向header发送了一个字符串。如果select元素中没有任何选项被选中,那么就会发出所显示的这个选项值。包含多个值的select元素(允许选择多个选项并且 用<select-multiple>表示的select元素)发出一个字符串 数组,并且必须通过SelectRequest.getParameterValues方法进 行处理。

复选框

复选框比较奇特。核查过的复选框会发送字符串”on“到服务器。未经核查的复选框则不向服务器发送任何内容,此时ServletRequest.getParameter(fieldName)将返回null

单选框

单选框将被选中按钮的值发送到服务器。如果没有选择任何按钮,将没有任何内容被发送到服务器,此时ServletRequest.getParameter(fieldName)将返回null

多个同名元素的情况

如果一个表单中包含多个输入同名的元素,那么所 有值都会被提交,此时必须利用 ServletRequest.getParameterValues方法来获取它们。 如果你使用ServletRequest.getParameter方法的话将只返回最后一个值。

实例

下面的FormServlet类示范了如何处理HTML 表单。它的**doGet方法将一个Order表单发送到浏览器。 它的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
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
package app01a;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(
name = "FormServlet",
urlPatterns =
{"/form"}
)
public class FormServlet extends HttpServlet
{
private static final long serialVersionUID = 54L;
private static final String TITLE = "Order Form";
@Override
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException,IOException
{
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.println("<html>");
writer.println("<head>");
writer.println("<title>" + TITLE + "</title></head>");
writer.println("<body><h1>" + TITLE + "</h1>");
writer.println("<form method='post'>");
writer.println("<table>");
writer.println("<tr>");
writer.println("<td>Name:</td>");
writer.println("<td><input name='name'/></td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Address:</td>");
writer.println("<td><textarea name='address' "
+ "cols='40' rows='5'></textarea></td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Country:</td>");
writer.println("<td><select name='country'>");
writer.println("<option>United States</option>");
writer.println("<option>Canada</option>");
writer.println("</select></td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Delivery Method:</td>");
writer.println("<td><input type='radio' " +
"name='deliveryMethod'"
+ " value='First Class'/>First Class");
writer.println("<input type='radio' " +
"name='deliveryMethod' "
+ "value='Second Class'/>Second Class</td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Shipping Instructions:</td>");
writer.println("<td><textarea name='instruction' "
+ "cols='40' rows='5'></textarea></td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>&nbsp;</td>");
writer.println("<td><textarea name='instruction' "
+ "cols='40' rows='5'></textarea></td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Please send me the latest " +
"product catalog:</td>");
writer.println("<td><input type='checkbox' " +
"name='catalogRequest'/></td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>&nbsp;</td>");
writer.println("<td><input type='reset'/>" +
"<input type='submit'/></td>");
writer.println("</tr>");
writer.println("</table>");
writer.println("</form>");
writer.println("</body>");
writer.println("</html>");
}
@Override
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException,IOException
{
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.println("<html>");
writer.println("<head>");
writer.println("<title>" + TITLE + "</title></head>");
writer.println("</head>");
writer.println("<body><h1>" + TITLE + "</h1>");
writer.println("<table>");
writer.println("<tr>");
writer.println("<td>Name:</td>");
writer.println("<td>" + request.getParameter("name")
+ "</td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Address:</td>");
writer.println("<td>" + request.getParameter("address")
+ "</td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Country:</td>");
writer.println("<td>" + request.getParameter("country")
+ "</td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Shipping Instructions:</td>");
writer.println("<td>");
String[] instructions = request
.getParameterValues("instruction");
if(instructions != null) {
for(String instruction: instructions) {
writer.println(instruction + "<br/>");
}
}
writer.println("</td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Delivery Method:</td>");
writer.println("<td>"
+ request.getParameter("deliveryMethod")
+ "</td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Catalog Request:</td>");
writer.println("<td>");
if(request.getParameter("catalogRequest") == null) {
writer.println("No");
} else {
writer.println("Yes");
}
writer.println("</td>");
writer.println("</tr>");
writer.println("</table>");
writer.println("<div style='border:1px solid #ddd;" +
"margin-top:40px;font-size:90%'>");
writer.println("Debug Info<br/>");
Enumeration<String> parameterNames = request
.getParameterNames();
while(parameterNames.hasMoreElements()) {
String paramName = parameterNames.nextElement();
writer.println(paramName + ": ");
String[] paramValues = request
.getParameterValues(paramName);
for(String paramValue: paramValues) {
writer.println(paramValue + "<br/>");
}
}
writer.println("</div>");
writer.println("</body>");
writer.println("</html>");
}
}

利用下面的URL,可以调用FormServlet
http://localhost:8080/Hello/app01b/form
这样Servlet容器会调用的doGet方法,doGet()方法会如下的HTML表单发送给浏览器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<form method='post'>
<input name='name' />
<textarea name='address' cols='40' rows='5'></textarea>
<select name='country'>");
<option>United States</option>
<option>Canada</option>
</select>
<input type='radio' name='deliveryMethod' value='First Class' />
<input type='radio' name='deliveryMethod' value='Second Class' />
<textarea name='instruction' cols='40' rows='5'></textarea>
<textarea name='instruction' cols='40' rows='5'></textarea>
<input type='checkbox' name='catalogRequest' />
<input type='reset' />
<input type='submit' />
</form>

然后浏览器将解析这个HTML并显示,浏览器中的显示效果如下:
这里有一张图片
get请求响应得到的表单的方法设为post,确保当用户提交表单时,使用HTTP POST方法提交。**而它的action属性默认,这表示该表单会被提交给请求它时用的相同的URL**,还是http://localhost:8080/Hello/form这个URL.
现在,填写表单,并单击Submit按钮。如下图所示:
这里有一张图片
因为表单设置的方法为post,所以我们在表单中输入的值,将通过HTTP POST方法被发送给服务器,这 样就会调用ServletdoPost方法。doPost()方法给出响应.浏览器得到响应后显示如下:
这里有一张图片

总结

  • 表单中的action属性若为空,则表单数据将提交给到当前URL.
  • 当第一次输入URL是使用的是get请求,调用的是HttpServletdoGet方法.

1.9.3 HttpServletResponse接口

HttpServletResponse表示HTTP环境中的Servlet响应。下面是它里面定义的部分方法:

方法 描述
void addCookie(Cookie cookie) 给这个响应对象添加一个cookie
void addHeader(java.lang.String name, java.lang.String value) 给这个响应对象添加一个header
void sendRedirect(java.lang.String location) 发送一条响应码,将浏览器跳转到指定的位置,也就是请求重定向