会话可简单理解为用户开一个浏览器,点击多个超链接,访问服务器多个Web资源,然后关闭浏览器,整个过程称之为一个会话。一个同学来过教室,下次再来教室,我们会知道这个同学曾经来过,这称之为有状态会话。
每个用户在使用浏览器与服务器进行会话的过程中,不可避免各自会产生一些数据,程序要想办法为每个用户保存这些数据。例如,用户点击超链接通过一个Servlet购买了一个商品,程序应该想办法保存用户购买的商品,以便于用户点结帐Servlet时,结帐Servlet可以得到用户购买的商品为用户结帐。
那么我们试着思考一下,在上面的例子中,用户购买的商品保存在request或servletContext中行不行?显然不行。所以,接下来就引出了本文所要讲解的技术了。
HTTP的请求是无状态的,即客户端与服务器在通讯的时候,是无状态的。其实就是客户端在第二次来访的时候,服务器根本就不知道这个客户端以前有没有来访问过。为了更好的用户体验,更好的交互(比如自动登录),其实从公司层面讲,就是为了更好的收集用户习惯(比如大数据)。
javax.servlet.http.Cookie类用于创建一个Cookie。Cookie类的主要方法如下表所示:
由于setMaxAge和setPath这两个方法比较重要,我还是想再解释一下这两个方法的意思。
最后,response接口中定义了一个addCookie方法,它用于在其响应头中增加一个相应的Set-Cookie头字段。同样,request接口中也定义了一个getCookies方法,它用于获取客户端提交的Cookie。
下面是一个代表网站首页的Servlet。
package cn.liayun.cookie;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
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("/CookieDemo1")
public class CookieDemo1 extends HttpServlet {
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();
out.print("您上次访问时间是:");
//获取浏览器访问服务器时传递过来的Cookie数组
Cookie[] cookies = request.getCookies();
/*
* 获得用户的时间Cookie
* 如果用户是第一次访问,那么得到的cookies将是null
*/
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.toLocaleString());//转成中国人喜欢看的格式
}
}
/*
* 给用户回送最新的访问时间
* 用户访问过之后重新设置用户的访问时间,存储到Cookie中,然后发送到客户端浏览器
*/
Cookie cookie = new Cookie("lastAccessTime", System.currentTimeMillis() + "");//创建一个Cookie,Cookie的名字是lastAccessTime
//将Cookie对象添加到response对象中,这样服务器在输出response对象中的内容时就会把Cookie也输出到客户端浏览器
response.addCookie(cookie);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
当第一次访问时这个Servlet时,效果如下所示:
点击浏览器的刷新按钮,进行第二次访问,此时服务器就可以通过Cookie获取浏览器上一次访问的时间了,效果如下:
在上面的例子中,在程序代码中并没有使用setMaxAge方法设置Cookie的有效期,所以当关闭浏览器之后,Cookie就失效了,要想在关闭了浏览器之后,Cookie依然有效,那么在创建Cookie时,就要为Cookie设置一个有效期。如下所示:
当第一次访问时这个Servlet时,效果如下所示:
用户第一次访问时,服务器发送给浏览器的Cookie就存储到了硬盘上,市面上有各种浏览器,那么我们又该如何通过这些浏览器查看保存的Cookie呢?可以参考我的另一篇博客——
如何通过浏览器查看保存在本地磁盘的Cookie?。这样即使关闭了浏览器,下次再访问时,也依然可以通过Cookie获取用户上一次访问的时间。
首先,我们要将代表网站首页的Servlet修改一下。
package cn.liayun.cookie;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
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("/CookieDemo1")
public class CookieDemo1 extends HttpServlet {
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();
out.print("清除上次访问时间
");
out.print("您上次访问时间是:");
//获取浏览器访问服务器时传递过来的Cookie数组
Cookie[] cookies = request.getCookies();
/*
* 获得用户的时间Cookie
* 如果用户是第一次访问,那么得到的cookies将是null
*/
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.toLocaleString());//转成中国人喜欢看的格式
}
}
/*
* 给用户回送最新的访问时间
* 用户访问过之后重新设置用户的访问时间,存储到Cookie中,然后发送到客户端浏览器
*/
Cookie cookie = new Cookie("lastAccessTime", System.currentTimeMillis() + "");//创建一个Cookie,Cookie的名字是lastAccessTime
cookie.setMaxAge(1 * 24 * 3600);
cookie.setPath("/day07");
//将Cookie对象添加到response对象中,这样服务器在输出response对象中的内容时就会把Cookie也输出到客户端浏览器
response.addCookie(cookie);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
然后,还要编写一个清除上次访问时间的Servlet。
package cn.liayun.cookie;
import java.io.IOException;
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("/CookieDemo2")
public class CookieDemo2 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*
* 要删除Cookie,那么这两个Cookie必须一模一样
*/
//创建一个名字为lastAccessTime的Cookie
Cookie cookie = new Cookie("lastAccessTime", System.currentTimeMillis() + "");
//将Cookie的有效期设置为0,命令浏览器删除该Cookie
cookie.setMaxAge(0);
cookie.setPath("/day07");
response.addCookie(cookie);
//重定向到CookieDemo1
//response.sendRedirect("/day07/CookieDemo1");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
温馨提示:删除Cookie时,path必须一致,否则不会删除。
要想在Cookie中存储中文,那么必须使用URLEncoder类里面的encode(String s, String enc)方法进行中文转码,例如:
在获取Cookie中的中文数据时,再使用URLDecoder类里面的decode(String s, String enc)进行解码,例如:
例,用户在上网购买商品时,通常会在网站的左侧显示用户上次浏览过的商品,如:
显示上次浏览商品的实现过程如下动图所示。
温馨提示,当用户第二次点击查看某个商品时,绝对不是回送一个新的Cookie,而是得到用户原来的Cookie,在原来Cookie的后面加上商品id号回写给浏览器。
首先设计一个代表图书的JavaBean,即Book.java。
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;
}
}
然后设计一个代表数据库的类,即Db.java。在设计该类时需要注意以下几点。
//代表数据库
class Db {
private static Map<String, Book> map = new LinkedHashMap<String, Book>();
static {
map.put("1", new Book("1", "Java Web开发", "老张", "一本好书!!!"));
map.put("2", new Book("2", "JDBC开发", "老张", "一本好书!!!"));
map.put("3", new Book("3", "Spring开发", "老黎", "一本好书!!!"));
map.put("4", new Book("4", "Struts2开发", "老毕", "一本好书!!!"));
map.put("5", new Book("5", "Android开发", "老黎", "一本好书!!!"));
}
public static Map<String, Book> getAll() {
return map;
}
}
接着设计代表首页的Servlet(CookieDemo3.java)。此类在设计时应分为两部分:
package cn.liayun.cookie;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.LinkedHashMap;
import java.util.Map;
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;
//代表首页的Servlet
@WebServlet("/CookieDemo3")
public class CookieDemo3 extends HttpServlet {
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.print("本网站有如下商品:
");
Map<String, Book> map = Db.getAll();
for (Map.Entry<String, Book> entry : map.entrySet()) {
Book book = entry.getValue();
out.print("" + book.getName() + "
");
}
//2.显示用户曾经看过的商品
out.print("
您曾经看过如下商品:
");
Cookie[] cookies = request.getCookies();
for (int i = 0; cookies != null && i < cookies.length; i++) {
if (cookies[i].getName().equals("bookHistory")) {
//cookies[i].getValue()返回的应该是2,3,1这样的形式
String[] ids = cookies[i].getValue().split("\\,");//2,3,1
for (String id : ids) {
Book book = Db.getAll().get(id);
out.print(book.getName() + "
");
}
}
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
最后设计显示商品详细信息的Servlet(CookieDemo4.java)。此类在设计时也应分为两部分:
在构建Cookie时,涉及到把数组变成List集合,此时用到Arrays工具类的1个常见方法:public static
,返回一个受指定数组支持的固定大小的列表。把数组变成List集合有什么好处呢?可以使用集合的思想和方法来操作数组中的元素。温馨提示:将数组变成集合,不可以使用集合的增删方法,因为数组的长度是固定的;但可以使用集合的诸如contains()、get()、indexOf()、subList()等等方法,如果你增删,那么会生成UnsupportedOperationException异常。这里,因为我们需要使用List集合频繁增删,所以需要用到LinkedList类,其底层使用的是链表数据结构,增删速度会很快。
package cn.liayun.cookie;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.LinkedList;
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;
//显示商品详细信息的Servlet(难点)
@WebServlet("/CookieDemo4")
public class CookieDemo4 extends HttpServlet {
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.根据用户带过来的id,显示相应商品的详细信息
String id = request.getParameter("id");
Book book = Db.getAll().get(id);
out.print(book.getId() + "
");
out.print(book.getAuthor() + "
");
out.print(book.getName() + "
");
out.print(book.getDescription() + "
");
//2.构建Cookie,回写给浏览器
String cookieValue = buildCookie(id, request);
Cookie cookie = new Cookie("bookHistory", cookieValue);
cookie.setMaxAge(1 * 30 * 24 * 60 * 60);
cookie.setPath("/day07");
response.addCookie(cookie);
}
private String buildCookie(String id, HttpServletRequest request) {
//① bookHistory = null 1 1
//② bookHistory = 2,5,1 1 1,2,5
//③ bookHistory = 2,5,4 1 1,2,5
//④ bookHistory = 2,5 1 1,2,5
String bookHistory = null;
Cookie[] cookies = request.getCookies();
for (int i = 0; cookies != null && i < cookies.length; i++) {
if (cookies[i].getName().equals("bookHistory")) {
bookHistory = cookies[i].getValue();//使用bookHistory记住浏览器带过来的Cookie的值
}
}
if (bookHistory == null) {
return id;
}
/*
if (bookHistory.contains(id)) {//21,5,4 1
}
*/
LinkedList<String> list = new LinkedList(Arrays.asList(bookHistory.split("\\,")));//将数组转成集合
/*
if (list.contains(id)) {
//② bookHistory = 2,5,1 1 1,2,5
list.remove(id);
list.addFirst(id);
} else {
if (list.size() >= 3) {
//③ bookHistory = 2,5,4 1 1,2,5
list.removeLast();
list.addFirst(id);
} else {
//④ bookHistory = 2,5 1 1,2,5
list.addFirst(id);
}
}
*/
//以上代码优化之后
if (list.contains(id)) {
list.remove(id);
} else {
if (list.size() >= 3) {
list.removeLast();
}
}
list.addFirst(id);
StringBuffer sb = new StringBuffer();
for (String bid : list) {
sb.append(bid + ",");
}
return sb.deleteCharAt(sb.length() - 1).toString();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}