1 . 什么是会话?
会话可简单理解为:用户开一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭浏览器,整个过程称之为一个会话。
1.1 会话过程中要解决的一些问题?
每个用户在使用浏览器与服务器进行会话的过程中,不可避免各自会产生一些数据,程序要想办法为每个用户保存这些数据。
例如:用户点击超链接通过一个servlet购买了一个商品,程序应该想办法保存用户购买的商品,以便于用户点结帐servlet时,结帐servlet可以得到用户购买的商品为用户结帐。
思考:用户购买的商品保存在request或servletContext中行不行?
假如用使用request保存购买商品的servlet中的信息,当用户访问结账servlet时,又重新创建了一个新的request,所以使用request不行。
使用ServletContext也不行,因为ServletContext是针对整个web应用的。
1.2 保存会话数据的技术
保存会话有两种技术:Cookie和Session。
2. Cookie
Cookie是客户端技术,程序把每个用户的数据以cookie的形式写给用户各自的浏览器。当用户使用浏览器再去访问服务器中的web资源时,就会带着各自的数据去。这样,web资源处理的就是用户各自的数据了。
2.1 显示用户上次登录的时间
package com.oner.cookie; import java.io.IOException; import java.io.PrintWriter; import java.util.Date; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; //假设CookieDemo这个Servlet代表网站首页 public class CookieDemo extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); response.setHeader("Expires", "-1"); response.setHeader("Cache-Control", "no-cache"); response.setHeader("Pragma", "no-cache"); PrintWriter out = response.getWriter(); out.print("您上次访问的时间是"); // 获得用户的时间cookie Cookie[] cookies = request.getCookies(); // 用户第一次访问时并没有带Cookie,所以需要先检查下 for (int i = 0; cookies != null && i < cookies.length; i++) { if (cookies[i].getName().equals("lastAccessTime")) { long cookieValue = Long.parseLong(cookies[i].getValue());// 得到了上次访问的时间 Date date = new Date(cookieValue); out.print(date.toString()); } } // 给用户发送最新的访问时间 Cookie cookie = new Cookie("lastAccessTime", System.currentTimeMillis() + ""); //获得的是自1970-1-01到现在的毫秒,类型为long
cookie.setMaxAge(1 * 30 * 24 * 3600);// 设置Cookie有效期为一个月
cookie.setPath("/day07");
response.addCookie(cookie);
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
在浏览器地址中输入:http://localhost:8080/day07/CookieDemo,回车:
刷新后:
2.2 Cookie的一些细节
1. 一个Cookie只能标识一种信息,它至少含有一个标识该信息的名称(NAME)和设置值(VALUE)。
2. 一个WEB站点可以给一个WEB浏览器发送多个Cookie,一个WEB浏览器也可以存储多个WEB站点提供的Cookie。
3. 浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。
4. 如果创建了一个cookie,并将他发送到浏览器,默认情况下它是一个会话级别的cookie(即存储在浏览器的内存中),用户退出浏览器之后即被删除。若希望浏览器将该cookie存储在磁盘上,则需要使用maxAge,并给出一个以秒为单位的时间。将最大时效设为0则是命令浏览器删除该cookie。需要注意,删除cookie时,path必须一致,否则不会删除。
这儿演示下:
先使用火狐浏览器搜索Cookie选项,搜索“lastAccessTime”,发现没有lastAccessTime文件:
在地址栏中输入:http://localhost:8080/day07/CookieDemo,回车:
再次搜索名为“lastAccessTime”的cookie文件,发现确实存在:
说明浏览器确实得到了该cookie,再次刷新地址栏:
说明第二次访问时服务器从浏览器发送到请求中得到了“lastAccessTime”cookie信息。点击“点击上次访问时间”:
地址跳转到了http://localhost:8080/day07/DeleteCookie,说明执行了DeleteCookie这个Servlet,在Cookie选项中搜索“lastAccessTime”这条Cookie,发现没有:
说明删除成功。
2.3 显示商品浏览历史记录
假如一个网上书店需要在用户每次登陆主页后显示用户曾经浏览过的一些书籍,该怎么实现呢?
思考下,这儿至少需要两个Servlet,一个是ServletA,一个是ServletB。ServletA代表网上书店的主页,主要是用于显示书店的所有书籍以及曾经浏览的一些书籍(按照浏览时间的递减顺序显示,也就是最后浏览的书籍显示在最上面):
ServletB主要用于显示书籍的详细信息:
显示书店的所有书籍以及显示书籍的详细信息很容易实现,可以从数据库中得到,这儿用一个Book类和DB类来模拟数据库:
完整的代码如下:
ServletA:
package com.oner.cookie;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//代表书店首页的Servlet
public class ServletA extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
// 1. 输出网站的所有书籍
out.write("本网站有如下书籍:<br/>");
Map<String, Book> map = DB.getAll();
Set<Map.Entry<String, Book>> set = map.entrySet();
for (Map.Entry<String, Book> entry : set) {
Book book = entry.getValue();
// 这儿的超链接打开方式设为在新的窗口打开
// 并且需要将每次所浏览书的id给ServletB
out.print("<a href='/day07/ServletB?id=" + book.getId()
+ "' target='_blank'>" + book.getName() + "</a>");
out.write("<br/>");
}
// 4. 从浏览器带来的Cookie中,得到用户曾经看过的书籍
out.print("<br/><br/>您曾经浏览过的书籍:<br/>");
Cookie[] cookies = request.getCookies();
for (int i = 0; cookies != null && i < cookies.length; i++) {
if (cookies[i].getName().equals("bookHistory")) {
String[] ids = cookies[i].getValue().split("\,");// 得到浏览过所有书籍的id集合{2,3,1}
for (String id : ids) {
Book book = DB.getAll().get(id);
out.print(book.getName() + "<br/>");
}
}
}
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
class DB {
// 使用LinkedHashMap是为了保证存储和取出的顺序一致
private static Map<String, Book> map = new LinkedHashMap<String, Book>();
static {
map.put("1", new Book("1", "javaweb开发", "老张", "一本好书"));
map.put("2", new Book("2", "spring开发", "老黎", "一本好书"));
map.put("3", new Book("3", "hibernate开发", "老佟", "一本好书"));
map.put("4", new Book("4", "struts开发", "老毕", "一本好书"));
map.put("5", new Book("5", "ajax开发", "老张", "一本好书"));
}
public static Map<String, Book> getAll() {
return map;
}
}
class Book {
private String id;
private String name;
private String author;
private String description;
public Book() {
super();
}
public Book(String id, String name, String author, String description) {
super();
this.id = id;
this.name = name;
this.author = author;
this.description = description;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
ServletB:
package com.oner.cookie;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//显示商品详细信息的Servlet
public class ServletB extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
// 2. 根据用户带过来的id,显示商品的详细信息
String id = request.getParameter("id");
Book book = DB.getAll().get(id);
out.write("您要查看的书的详细信息如下:<br/><br/>");
out.print("书名:" + book.getName() + "<br/>");
out.print("作者:" + book.getAuthor() + "<br/>");
out.print("描述:" + book.getDescription() + "<br/>");
// 3. 构建Cookie,回写给浏览器
// 根据当前浏览书籍的id和本次request中包含的bookHistory来构建一个新的bookHistory
String cookieValue = buildCookie(id, request);
Cookie cookie = new Cookie("bookHistory", cookieValue);
cookie.setMaxAge(1 * 30 * 24 * 3600);
cookie.setPath("/day07");
response.addCookie(cookie);
}
private String buildCookie(String id, HttpServletRequest request) {
// bookHistory=null 1 bookHistory=1
// bookHistory=3_1_5 1 bookHistory=1_3_5
// bookHistory=3_2_5 1 bookHistory=1_3_2
// bookHistory=3_2 1 bookHistory=1_3_2
String bookHistory = null;
Cookie[] cookies = request.getCookies();
// 从浏览器带来的cookie中得到bookHistory,并将其赋值给本地变量bookHistory
for (int i = 0; cookies != null && i < cookies.length; i++) {
if (cookies[i].getName().equals("bookHistory")) {
bookHistory = cookies[i].getValue();
}
}
// ①如果bookHistory=null,说明浏览器之前并没有访问过本网站的书籍,所以返回当前浏览的书籍的id
if (bookHistory == null) {
return id;
}
// bookHistory字符串中包含的是之前浏览书籍的所有id,且以“,”分开
// 所以可以将其切分成一个数组
String[] srcArray = bookHistory.split("\,");
// 将数组转成List集合
List<String> srcList = Arrays.asList(srcArray);
// 查看asList方法的源码,知道该方法返回的其实是一个ArrayList
// 由于LinkedList集合的增删快,所以可以将ArrayList集合转成LinkedList
LinkedList<String> list = new LinkedList<String>(srcList);
// ②如果bookHistory中包含当前浏览书籍的id,那么就将当前浏览书籍的id放在最前面
if (list.contains(id)) {
list.remove(id);
list.addFirst(id);
} else {
// ③如果bookHistory中包含的书籍id大于等于3,则需要移去最后的id,并将当前浏览书籍的id放在最前面
if (list.size() >= 3) {
list.removeLast();
list.addFirst(id);
} else {
// ④bookHistory包含的书籍id小于3,且不含当前浏览书籍的id,则直接将当前浏览书籍的id加入到最前面
list.addFirst(id);
}
}
StringBuffer sb = new StringBuffer();
for (String l : list) {
sb.append(l).append(",");
}
// 去掉左后一个无用的逗号
return sb.deleteCharAt(sb.length() - 1).toString();
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
测试:
打开httpwatch,在浏览器地址中输入:http://localhost:8080/day07/ServletA,回车:
点击spring开发这本书籍,浏览器开启了一个新的页面显示这本书的详细信息:
查看httpwatch:
从响应头Set-Cookie中可以看出来,服务器发送了一个名为“bookHistory”,值为2的Cookie。其实这时候,浏览器会将这个cookie缓存到本地磁盘中。再次回到主页,点击javaweb开发这本书,发现浏览器开启了一个新的页面显示这本书的详细信息:
查看httpwatch:
从发送头Cookie可以看到,bookHistory=2,说明浏览器这次是携带着上次缓存到本地磁盘的cookie来访问的;从响应头的Set-Cookie可以看出,bookHistory=“1,2”,由此说明了ServletB更新了bookHistory,将其值赋为本次访问的书籍id1以及之前访问的书籍id2。这时回到主页刷新下: