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

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>");
}