目录
会话管理或会话追踪是 Web 应用程序开发中非常重要的一部分。这是因为 Web 语言 HTTP 是无状态的。在默认情况下,Web 服务器不知道一个 HTTP 请求是来自初次用户,还是来自之前已经访问过的用户。//进行会话管理的根本原因,Http是无状态的
例如,Web Mail 应用程序要求其用户在查看邮件之前要先登录。但是,一旦用户输入正确的用户名和密码,用户在访问应用程序的其他部分时,就不应该再次提示他们登录。应用程序需要记住哪些用户已经登录成功。换句话说,它必须能够管理用户的会话。
在 Web 应用中可以用于保持状态的 4 种方法:网址重写(URL rewriting)、使用隐藏域、使用 cookie、使用 HttpSession 对象。
1、网址重写(URL rewriting)
网址重写是一种会话追踪技术,需要将一个或多个 token 作为一个查询字符串添加到一个 URL 中。token 的格式一般是键值对://其实就是传参,通过特定参数对资源进行定位
url?key_1=value_1&key_2=value_2 ...&key_n=value_n
如果 token 不必在过多的 URL 中四处携带,那么网址重写就比较合适。采用网址重写的缺点如下:
- 在有些 Web 浏览器中,URL 限制为 2000 个字符。
- 仅当有链接要插入值时,值才能转换成后面的资源。此外,要把值添加到静态页面的链接中,不是一件容易的事情。
- 网址重写必须在服务器端有效。所有的链接都必须带有值,这样可能出现一个问题:即一个页面中可能会有许多个链接。
- 某些字符,例如空格、& 符号及问号都必须进行编码。
- 添加到 URL中的信息是明显可见的,安全性能低。
由于上述局限性,网址重写只适用于那些既需要保持状态,却又不跨越太多页面,并且又不太重要的信息。//局限性非常明显,生产环境基本可以无视了
2、隐藏域
利用隐藏域来保持状态,与采用网址重写技术类似。但它不是将值添加到 URL 后面,而是将它们放在 HTML 表单的隐藏域中。当用户提交表单时,隐藏域中的值也传送到服务器。//使用隐藏域进行传参
只有当页面包含表单,或者可以在页面中添加表单时,才适合使用隐藏域。
这种技术胜过网址重写技术的地方在于,可以将更多的字符传到服务器,并且不需要进行字符编码。但是像网址重写一样,也只有当要传递的信息不需要跨越多个页面时,才适合使用这种技术。
3、Cookie 技术
网址重写和隐藏域都只适用于保持那些不需要跨越许多页面的信息。如果这些信息需要跨越很多页面,这两种技术就变得很难实现,因为你必须管理每一个页面的信息。值得庆幸的是,cookie 能够解决网址重写和隐藏域都无法解决的问题。//为什么需要会话技术的原因
cookie 是自动地在 Web 服务器和浏览器之间来回传递的一小块信息。
cookie 属于客户端会话技术,它是服务器发送给浏览器的小段文本信息,存储在客户端浏览器的内存中或硬盘上。当浏览器保存了 cookie 后,每次访问同一个服务器,都会在 http 请求头中将这个 cookie 回传给服务器。
cookie 适用于那些需要跨越许多页面的信息。由于 cookie 是作为 HTTP 标头嵌入的,因此传输它的过程由 HTTP 协议处理。除此之外,还可以根据自己的需要设置 cookie 的有效期限。对于 Web 浏览器而言,每台 Web 服务器最多可以支持 20 个 cookie。//cookie会被http请求头自动携带
cookie 的不足之处在于,用户可以通过修改浏览器设置来拒绝接受 cookie。//导致cookie失效
cookie 虽然可以解决服务器跟踪用户状态的问题,但是它具有以下缺点:
- 在 HTTP 请求中,Cookie 是明文传递的,容易泄露用户信息,安全性不高。
- 浏览器可以禁用 Cookie,一旦被禁用,Cookie 将无法正常工作。
- Cookie 对象中只能设置文本(字符串)信息。
- 客户端浏览器保存 Cookie 的数量和长度是有限制的。
3.1 - 如何使用 Cookie
要使用cookie,必须熟悉 javax.servlet.http.Cookie 类,以及 HttpServletRequest 和HttpServletResponse 接口中的几个方法。
要创建 cookie,需要传递一个名称和一个值给 Cookie 类的构造器
Cookie cookie = new Cookie(name, value);
例如,要创建一个选择语言的 cookie,可以这么写:
Cookie languageCookie = new Cookie("language","Italian");
创建 Cookie 之后,可以设置它的 domain、path 及 maxAge 属性。尤其值得关注的是 maxAge 属性,因为它决定 cookie 的有效期限。
为了将一个 cookie 发送到浏览器,需在 HttpServletResponse 上调用 add 方法:
httpServletResponse.addCookie(cookie);
当浏览器再次发出对同一个资源或者对同一台服务器中的不同资源的请求时,它会同时把从 Web 浏览器处收到的 cookie 再传回去。
要访问浏览器发出的 cookie,可以在 HttpServletRequest 中使用 getCookies 方法。该方法将返回一个 Cookie 数组,如果请求中没有 cookie,将返回 null。为了找到某个名称的 cookie,需要迭代数组。下面举个例子,看看如何读取一个名为 maxRecords 的 cookie。
Cookie[] cookies = request.getCookies();
Cookie maxRecordsCookie = null;
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("maxRecords")) {
maxRecordsCookie = cookie;
break;
}
}
}
令人遗憾的是,没有 getCookieByName 方法可以使获取 cookie 变得更简单一些。更令人难过的是,也没有方法可以直接删除 cookie。为了删除 cookie,需要创建一个同名的 cookie,将它的 maxAge 属性设置为 0,并在 HttpServletResponse 中添加一个新的 cookie。看看下面是如何删除一个名为 userName 的 cookie 的:
Cookie cookie = new Cookie("userName", "");
cookie.setMaxAge(0);
response.addCookie(cookie);
3.2 - Cookie 类
Cookie 封装由 Servlet 发送给 Web 浏览器的少量信息,由浏览器保存,然后发送回服务器。Cookie 的值可以唯一地标识客户端,因此 Cookie 通常用于会话管理。
Cookie有名称、单个值和可选属性,如注释、路径和域限定符、最长使用时间和版本号。
Servlet 通过使用 HttpServletResponse.addCookie(javax.servlet.http.Cookie) 方法向浏览器发送 Cookie,该方法将字段添加到 HTTP 响应头中,以便每次响应时向浏览器发送 Cookie。浏览器预计为每个 Web 服务器支持 20 个 Cookie,总共 300 个Cookie,并且可能将每个 Cookie 的大小限制为4 KB。//Cookie有数量和大小限制
浏览器通过向 HTTP 请求头中添加字段来向 Servlet 返回 Cookie。可以使用 HttpServletRequest.getCookies() 方法从请求中检索 Cookie。多个 Cookie 可能具有相同的名称但是需要有不同的路径属性。
Cookie 类中的相关代码如下:
public class Cookie implements Cloneable, Serializable {
private static final long serialVersionUID = -6454587001725327448L;
private static final String TSPECIALS;
private static final String LSTRING_FILE ="javax.servlet.http.LocalStrings";
private static ResourceBundle lStrings = ResourceBundle.getBundle(LSTRING_FILE);
static {
if (Boolean.valueOf(System.getProperty("org.glassfish.web.rfc2109_cookie_names_enforced", "true"))) {
TSPECIALS = "/()<>@,;:\\\"[]?={} \t";
} else {
TSPECIALS = ",; ";
}
}
private String name; // NAME= ... "$Name" style is reserved
private String value; // value of NAME
// 在报头cookie字段中编码的属性。
private String comment; // ;Comment=VALUE ... describes cookie's use ;Discard ... implied by maxAge < 0
private String domain; // ;Domain=VALUE ... domain that sees cookie
private int maxAge = -1; // ;Max-Age=VALUE ... cookies auto-expire
private String path; // ;Path=VALUE ... URLs that see the cookie
private boolean secure; // ;Secure ... e.g. 使用SSL
private int version = 0; // ;Version=1 ... means RFC 2109++ style
private boolean isHttpOnly = false;
// 构造函数
public Cookie(String name, String value) {
if (name == null || name.length() == 0) {
throw new IllegalArgumentException(lStrings.getString("err.cookie_name_blank"));
}
if (!isToken(name) ||
name.equalsIgnoreCase("Comment") || // rfc2019
name.equalsIgnoreCase("Discard") || // 2019++
name.equalsIgnoreCase("Domain") ||
name.equalsIgnoreCase("Expires") || // (old cookies)
name.equalsIgnoreCase("Max-Age") || // rfc2019
name.equalsIgnoreCase("Path") ||
name.equalsIgnoreCase("Secure") ||
name.equalsIgnoreCase("Version") ||
name.startsWith("$")) {
String errMsg = lStrings.getString("err.cookie_name_is_token");
Object[] errArgs = new Object[1];
errArgs[0] = name;
errMsg = MessageFormat.format(errMsg, errArgs);
throw new IllegalArgumentException(errMsg);
}
this.name = name;
this.value = value;
}
//1-Cookie的注释:描述cookie用途的注释,如果浏览器向用户呈现cookie,则注释是有用的。
public void setComment(String purpose) {
comment = purpose;
}
public String getComment() {
return comment;
}
//2-cookie的域:如设置域名为.foo.com,那么cookie在www.foo.com中可见,a.b.foo.com中不可见
public void setDomain(String domain) {
this.domain = domain.toLowerCase(Locale.ENGLISH); // IE allegedly needs this
}
public String getDomain() {
return domain;
}
//3-Cookie的有效期:
//正值为Cookie有效期(以秒为单位)
//负值表示浏览器不需要进行持久存储,当浏览器退出时删除此Cookie,0则表示删除Cookie
public void setMaxAge(int expiry) {
maxAge = expiry;
}
public int getMaxAge() {
return maxAge;
}
//4-Cookie的路径:
//Cookie在指定目录及该目录的子目录中的所有页面都是可见的。
//例如,设置路径为'/catalog',那么Cookie在服务器'/catalog'路径下的所有目录都是可见的。
public void setPath(String uri) {
path = uri;
}
public String getPath() {
return path;
}
//5-Cookie的安全性:指示浏览器是否只能使用安全协议(如HTTPS或SSL)发送Cookie。
public void setSecure(boolean flag) {
secure = flag;
}
public boolean getSecure() {
return secure;
}
//6-Cookie的名称:返回Cookie的名称。创建后不能更改名称。
public String getName() {
return name;
}
//7-Cookie的值:给Cookie赋一个新值
public void setValue(String newValue) {
value = newValue;
}
public String getValue() {
return value;
}
//8-Cookie的版本:
public int getVersion() {
return version;
}
public void setVersion(int v) {
version = v;
}
//9-测试字符串:如果该字符串在Java语言中被视为保留令牌,则返回true。
private boolean isToken(String value) {
int len = value.length();
for (int i = 0; i < len; i++) {
char c = value.charAt(i);
if (c < 0x20 || c >= 0x7f || TSPECIALS.indexOf(c) != -1) {
return false;
}
}
return true;
}
//10-克隆方法:覆盖标准的java.lang.Object.clone方法,返回此Cookie的副本
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e.getMessage());
}
}
//11-HttpOnly标记:如果为true,表示Cookie不支持暴露给客户端脚本,可用于防止跨站点的脚本攻击
public void setHttpOnly(boolean isHttpOnly) {
this.isHttpOnly = isHttpOnly;
}
public boolean isHttpOnly() {
return isHttpOnly;
}
}
3.3 - Cookie 的代码使用示例
创建三个Servlet,分别为://代码比较繁琐,如果想展示效果,直接复制代码即可
- PreferenceServlet:用来创建和修改 cookie 信息
- CookieInfoServlet:展示当前会话的 cookie 信息
- CookieClassServlet:根据设置的 cookie 信息,进行对应展示
代码清单 PreferenceServlet 类:
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 = "PreferenceServlet", urlPatterns = {"/preference"})
public class PreferenceServlet extends HttpServlet {
private static final long serialVersionUID = 888L;
//为定义的三个Servlet添加超链接
public static final String MENU ="<div style='background:#e8e8e8; padding:15px'>"
+ "<a href='cookieClass'>Cookie Class</a> "
+ "<a href='cookieInfo'>Cookie Info</a> "
+ "<a href='preference'>Preference</a>" + "</div>";
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
writer.print("<html><head>"
+ "<title>Preference</title>"
+ "<style>"
+ "table {"
+ "font-size:small;"
+ "background:NavajoWhite "
+ "}"
+ "</style>"
+ "</head><body>"
+ MENU
+ "请选择以下设置值:"
+ "<form method='post'>"
+ "<table>"
+ "<tr>"
+ "<td>标题字体大小: </td>"
+ "<td>"
+ "<select name='titleFontSize'>"
+ "<option>large</option>"
+ "<option>x-large</option>"
+ "<option>xx-large</option>"
+ "</select>"
+ "</td>"
+ "</tr>"
+ "<tr>"
+ "<td>标题样式和粗细(多选): </td>"
+ "<td>"
+ "<select name='titleStyleAndWeight' multiple>"
+ "<option>italic</option>"
+ "<option>bold</option>"
+ "</select>"
+ "</td>"
+ "</tr>"
+ "<tr>"
+ "<td>记录展示条数: </td>"
+ "<td>"
+ "<select name='maxRecords'>"
+ "<option>5</option>"
+ "<option>10</option>"
+ "</select>"
+ "</td>"
+ "</tr>"
+ "<tr>"
+ "<td rowspan='2'>"
+ "<input type='submit' value='设置'/>"
+ "</td>"
+ "</tr>"
+ "</table>"
+ "</form>"
+ "</body></html>");
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//最大记录
String maxRecords = request.getParameter("maxRecords");
//标题样式和粗细
String[] titleStyleAndWeight = request.getParameterValues("titleStyleAndWeight");
//标题字体大小
String titleFontSize =request.getParameter("titleFontSize");
//设置Cookie
response.addCookie(new Cookie("maxRecords", maxRecords));
response.addCookie(new Cookie("titleFontSize",titleFontSize));
// 删除 titleFontWeight and titleFontStyle cookies
// Delete cookie by adding a cookie with the maxAge = 0;
// 先删除再创建
Cookie cookie = new Cookie("titleFontWeight", "");
cookie.setMaxAge(0);
response.addCookie(cookie);
cookie = new Cookie("titleFontStyle", "");
cookie.setMaxAge(0);
response.addCookie(cookie);
if (titleStyleAndWeight != null) {
for (String style : titleStyleAndWeight) {
if (style.equals("bold")) {
response.addCookie(new Cookie("titleFontWeight", "bold"));
} else if (style.equals("italic")) {
response.addCookie(new Cookie("titleFontStyle", "italic"));
}
}
}
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
writer.println("<html><head>"
+ "<title>Preference</title>"
+ "</head><body>" + MENU
+ "您的首选项已设置"
+ "<br/>"
+ "<br/>记录展示条数:" + maxRecords
+ "<br/>标题大小: " + titleFontSize
+ "<br/>标题样式:");
// 如果没有选择任何选项,标题样式将为 null
if (titleStyleAndWeight != null) {
writer.println("<ul>");
for (String style : titleStyleAndWeight) {
writer.print("<li>" + style + "</li>");
}
writer.println("</ul>");
}
writer.println("</body></html>");
}
}
PreferenceServlet 类跟据用户设置,创建 四个 Cookie,设置页面如下图:
代码清单 CookieClassServlet 类:
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 = "CookieClassServlet", urlPatterns = { "/cookieClass" })
public class CookieClassServlet extends HttpServlet {
private static final long serialVersionUID = 837369L;
private String[] methods = {
"clone", "getComment", "getDomain",
"getMaxAge", "getName", "getPath",
"getSecure", "getValue", "getVersion",
"isHttpOnly", "setComment", "setDomain",
"setHttpOnly", "setMaxAge", "setPath",
"setSecure", "setValue", "setVersion"
};
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Cookie[] cookies = request.getCookies();
Cookie maxRecordsCookie = null;
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("maxRecords")) {
maxRecordsCookie = cookie;
break;
}
}
}
int maxRecords = 5; // default
if (maxRecordsCookie != null) {
try {
maxRecords = Integer.parseInt(maxRecordsCookie.getValue());
} catch (NumberFormatException e) {
// do nothing, use maxRecords default value
}
}
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
writer.print("<html><head>"
+ "<title>Cookie Class</title>"
+ "</head><body>"
+ PreferenceServlet.MENU
+ "<div> javax.servlet.http.Cookie 类中的一些方法:");
writer.print("<ul>");
for (int i = 0; i < maxRecords; i++) {
writer.print("<li>" + methods[i] + "</li>");
}
writer.print("</ul>");
writer.print("</div></body></html>");
}
}
CookieClassServlet 类获取在 PreferenceServlet 类中设置的 Cookie,设置当前页展示的数据数量,展示页面如下所示:
代码清单 CookielnfoServlet 类:
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 = "CookieInfoServlet", urlPatterns = { "/cookieInfo" })
public class CookieInfoServlet extends HttpServlet {
private static final long serialVersionUID = 3829L;
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Cookie[] cookies = request.getCookies();
StringBuilder styles = new StringBuilder();
styles.append(".title {");
// 根据Cookie设置具体值
if (cookies != null) {
for (Cookie cookie : cookies) {
String name = cookie.getName();
String value = cookie.getValue();
if (name.equals("titleFontSize")) {
styles.append("font-size:" + value + ";");
} else if (name.equals("titleFontWeight")) {
styles.append("font-weight:" + value + ";");
} else if (name.equals("titleFontStyle")) {
styles.append("font-style:" + value + ";");
}
}
}
styles.append("}");
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
writer.print(
"<html>"
+ "<head>"
+ "<title>Cookie 信息展示</title>"
+ "<style>" + styles.toString() + "</style>"
+ "</head>"
+ "<body>"
+ PreferenceServlet.MENU
+ "<div class='title'>"
+ "会话管理中的cookie信息:"
+ "</div>");
writer.print("<div>");
// cookies will be null if there's no cookie
if (cookies == null) {
writer.print("此 HTTP 响应中没有 cookie !");
} else {
writer.println("<br/>这个 HTTP 响应中的 cookie 信息:");
for (Cookie cookie : cookies) {
writer.println("<br/>" + cookie.getName() + ":" + cookie.getValue());
}
}
writer.print("</div>");
writer.print("</body></html>");
}
}
CookielnfoServlet 类获取在 PreferenceServlet 类中设置的所有 Cookie 信息,循环遍历并展示,展示页面如下图:
4、HttpSession 对象
用户可以没有或者有一个 HttpSession,并且只能访问他/她自己的 HtpSession。
HttpSession 是用户第一次访问某个网站时自动创建的。通过在 HttpServletRequest 中调用 getSession 方法,可以获取用户的 HttpSession。getSession 有两个重载方法:
HttpSession getSession() //如果没有,则创建一个session
HttpSession getSession(boolean create) //true:无则创建,false:无则返回null
HttpSession 的 setAttribute 方法将一个值放在 HttpSession 中,其方法签名如下://为Session 设置值
void setAttribute(java.lang.String name, java.lang.Object value)
通过在 HttpSession 中调用 getAttribute 方法,同时传递一个属性名称,可以获取 HttpSession 中保存的对象。这个方法的签名如下://获取在Session中设置的值
java.lang.Object getAttribute(java.lang.String name)
注意,放在 HttpSession 中的值是保存在内存中的。因此,只能将尽可能小的对象放在里面,并且数量不能太多。即使现代的 Servlet 容器可以在内存将满时将 HttpSession 中的对象移到辅助存储设备中,但是这样会影响性能。因此,对于保存在 HttpSession 里面的内容一定要慎重。
此外,HttpSession 中保存的值不发送到客户端,而是 Servlet 容器为它所创建的每一个 HttpSession 生成一个唯一标识符,并将这个标识符作为一个 token 发送给浏览器,一般是作为一个名为 JSESSIONID 的 cookie,或者作为一个 sessionid 参数添加到 URL后面。在后续的请求中,浏览器会将这个 token 发送回服务器使服务器能够知道是哪个用户在发出请求。无论 Servlet 容器选择用哪一种方式传输 session 标识符,那都是在后台自动完成的,不需要你去做额外的处理工作。
4.1 - HttpSession 接口
HttpSession 提供了一种在 Web 站点访问中识别用户的方法,并可以存储有关该用户的信息。
会话信息仅适用于当前 web 应用程序(ServletContext),因此存储在一个 ServletContext 中的信息在另一个 ServletContext 中不可见。
HttpSession 接口中的方法如下:
public interface HttpSession {
//1-时间操作:
public long getCreationTime();//会话创建时间,从1970-1-1午夜开始以毫秒为单位。
public long getLastAccessedTime();//返回客户端最后一次发送与之关联的请求的时间
public void setMaxInactiveInterval(int interval); //设置会话超时间隔(以秒为单位)
public int getMaxInactiveInterval();
//2-唯一标识符:唯一标识符字符串由容器决定
public String getId();
//3-Servlet内容:获取Servlet上下文
public ServletContext getServletContext();
//4-Session内容:增删改查
public Object getAttribute(String name); //根据名称获取属性
public Enumeration<String> getAttributeNames();
public void setAttribute(String name, Object value);
public void removeAttribute(String name);
public void invalidate(); //失效会话,然后清除会话中的所有属性
//5-新会话标识:如果客户端不加入会话,返回true。
//例如,如果客户端禁用了cookie,那么在每个请求上都会有一个新的会话。
public boolean isNew();
}
4.2 - HttpSession 的代码使用示例
下边代码是使用 HttpSession 的代码示例,在这个示例中,创建了一个 ShoppingCartServlet 类。这个 Servlet 实现了小型的在线商店,里面有 4 种商品。它允许用户将商品添加到购物车中,并浏览它的内容。
代码清单 ShoppingCartServlet 类:
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 List<Product> products = new ArrayList<>();
private NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(Locale.US);
/**
* 产品初始化
* @throws ServletException
*/
@Override
public void init() throws ServletException {
products.add(new Product(1, "Bravo 32寸高清电视 Bravo 32' HDTV",
"Low-cost HDTV from renowned TV manufacturer",
159.95F));
products.add(new Product(2, "Bravo 蓝光播放器 Bravo BluRay Player",
"High quality stylish BluRay player", 99.95F));
products.add(new Product(3, "Bravo立体声系统 Bravo Stereo System",
"5 speaker hifi system with iPod player",
129.95F));
products.add(new Product(4, "Bravo iPod播放器 Bravo iPod player",
"An iPod plug-in that can play multiple formats",
39.95F));
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String uri = request.getRequestURI();
// 定义了三个get方法,分别为:产品列表,产品详情,购物车。通过uri进行匹配
if (uri.endsWith("/products")) {
sendProductList(response);
} else if (uri.endsWith("/viewProductDetails")) {
sendProductDetails(request, response);
} else if (uri.endsWith("viewCart")) {
showCart(request, response);
}
}
/**
* 添加产品
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int productId = 0;
int quantity = 0;
try {
productId = Integer.parseInt(request.getParameter("id"));
quantity = Integer.parseInt(request.getParameter("quantity"));
} catch (NumberFormatException e) {
}
Product product = getProduct(productId);
if (product != null && quantity >= 0) {
ShoppingItem shoppingItem = new ShoppingItem(product, quantity);
//创建Session,并在session中添加对象
HttpSession session = request.getSession();
List<ShoppingItem> cart = (List<ShoppingItem>) session.getAttribute(CART_ATTRIBUTE);
if (cart == null) {
cart = new ArrayList<ShoppingItem>();
session.setAttribute(CART_ATTRIBUTE, cart);
}
cart.add(shoppingItem);
}
sendProductList(response);
}
/**
* 展示产品列表
* @param response
* @throws IOException
*/
private void sendProductList(HttpServletResponse response) throws IOException {
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
writer.println("<html>"
+ "<head>"
+ "<title>Products</title>"
+ "</head>"
+ "<body>"
+ "<h2>产品 Products</h2>");
writer.println("<ul>");
for (Product product : products) {
writer.println("<li>" + product.getName() + "("+ currencyFormat.format(product.getPrice())+ ") (" + "<a href='viewProductDetails?id=" + product.getId() + "'>Details</a>)");
}
writer.println("</ul>");
writer.println("<a href='viewCart'>View Cart</a>");
writer.println("</body></html>");
}
private Product getProduct(int productId) {
for (Product product : products) {
if (product.getId() == productId) {
return product;
}
}
return null;
}
/**
* 展示产品详情
* @param request
* @param response
* @throws IOException
*/
private void sendProductDetails(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
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>Product Details</title>"
+ "</head>"
+ "<body>"
+ "<h2>Product Details</h2>"
+ "<form method='post' action='addToCart'>");
writer.println("<input type='hidden' name='id' " + "value='" + productId + "'/>");
writer.println("<table>");
writer.println("<tr><td>商品名称Name:</td><td>"+ product.getName() + "</td></tr>");
writer.println("<tr><td>商品描述Description:</td><td>"+ product.getDescription() + "</td></tr>");
writer.println("<tr>"
+ "<tr>"
+ "<td><input name='quantity'/></td>"
+ "<td>"
+ "<input type='submit' value='Buy'/>"
+ "</td>"
+ "</tr>");
writer.println(
"<tr>"
+ "<td colspan='2'>"
+ "<a href='products'>Product List</a>"
+ "</td>"
+ "</tr>");
writer.println("</table>");
writer.println("</form></body>");
} else {
writer.println("No product found");
}
}
/**
* 查看购物车
* @param request
* @param response
* @throws IOException
*/
private void showCart(HttpServletRequest request,HttpServletResponse response) throws IOException {
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
writer.println("<html>"
+ "<head>"
+ "<title>Shopping Cart</title>"
+ "</head>");
writer.println("<body><a href='products'>" + "产品列表 Product List</a>");
HttpSession session = request.getSession();
//System.out.println("session ID:"+session.getId());
List<ShoppingItem> cart = (List<ShoppingItem>) session.getAttribute(CART_ATTRIBUTE);
if (cart != null) {
writer.println("<table>");
writer.println(
"<tr>"
+ "<td style='width:150px'>数量 Quantity</td>"
+ "<td style='width:150px'>产品 Product</td>"
+ "<td style='width:150px'>价格 Price</td>"
+ "<td>Amount</td>"
+ "</tr>");
double total = 0.0;
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'>"
+ "Total:"+ currencyFormat.format(total)
+ "</td>"
+ "</tr>");
writer.println("</table>");
}
writer.println("</table></body></html>");
}
}
ShoppingCartServlet 类中定义的两个对象,Product 类和 ShoppingItem 类的代码清单如下:
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;
}
}
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 类的 Post 方法中(添加商品),创建了一个session,并通过 session 保存页面提交的商品内容。
ShoppingItem shoppingItem = new ShoppingItem(product, quantity);
//创建Session,并在session中添加对象
HttpSession session = request.getSession();
List<ShoppingItem> cart = (List<ShoppingItem>) session.getAttribute(CART_ATTRIBUTE);
if (cart == null) {
cart = new ArrayList<ShoppingItem>();
session.setAttribute(CART_ATTRIBUTE, cart);
}
cart.add(shoppingItem);
当用户访问购物车时,从 session 中获取上次请求中保存的内容,并进行展示:
HttpSession session = request.getSession();
List<ShoppingItem> cart = (List<ShoppingItem>) session.getAttribute(CART_ATTRIBUTE);
操作步骤如下,首先通过 /products 查看产品列表,产品列表展示页如下:
点击 Details,访问 产品详情页,输入产品的购买数量,点击 buy 进行购买,可以多添加几个不同的商品。
然后,会跳转到 产品列表页,点击 View Cart,就可以看到刚才添加的商品信息。
上边的代码示例比较完整,使用了 session 保存了购买商品的信息,达到记录会话信息的目的。需要注意的是,这些信息并不会通过 session 传递到客户端,只是传送了一个 sessionID,服务器端通过 sessionID 来进行会话管理。同时 session 机制也需要 cookie 的支持,禁用 cookie,会导致 session 失效。
至此,全文结束。