Java Web基础入门第十八讲 Servlet开发——使用Cookie进行会话管理

会话

什么是会话?

会话可简单理解为用户开一个浏览器,点击多个超链接,访问服务器多个Web资源,然后关闭浏览器,整个过程称之为一个会话。一个同学来过教室,下次再来教室,我们会知道这个同学曾经来过,这称之为有状态会话。

会话过程中要解决的一些问题?

每个用户在使用浏览器与服务器进行会话的过程中,不可避免各自会产生一些数据,程序要想办法为每个用户保存这些数据。例如,用户点击超链接通过一个Servlet购买了一个商品,程序应该想办法保存用户购买的商品,以便于用户点结帐Servlet时,结帐Servlet可以得到用户购买的商品为用户结帐。
那么我们试着思考一下,在上面的例子中,用户购买的商品保存在request或servletContext中行不行?显然不行。所以,接下来就引出了本文所要讲解的技术了。

保存会话数据的两种技术

  • Cookie
    Cookie是客户端技术,程序把每个用户的数据以Cookie的形式写给用户各自的浏览器。当用户使用浏览器再去访问服务器中的Web资源时,就会带着各自的数据去。这样,Web资源处理的就是用户各自的数据了。
    Java Web基础入门第十八讲 Servlet开发——使用Cookie进行会话管理_第1张图片
  • Session
    Session是服务器端技术,利用这个技术,服务器在运行时可以为每一个用户的浏览器创建一个其独享的Session对象,由于Session为用户浏览器独享,所以用户在访问服务器的Web资源时,可以把各自的数据放在各自的Session中,当用户再去访问服务器中的其它Web资源时,其它Web资源再从用户各自的Session中取出数据为用户服务。
    Java Web基础入门第十八讲 Servlet开发——使用Cookie进行会话管理_第2张图片

Cookie

为什么要有Cookie?

HTTP的请求是无状态的,即客户端与服务器在通讯的时候,是无状态的。其实就是客户端在第二次来访的时候,服务器根本就不知道这个客户端以前有没有来访问过。为了更好的用户体验,更好的交互(比如自动登录),其实从公司层面讲,就是为了更好的收集用户习惯(比如大数据)。

操作Cookie的API

javax.servlet.http.Cookie类用于创建一个Cookie。Cookie类的主要方法如下表所示:
Java Web基础入门第十八讲 Servlet开发——使用Cookie进行会话管理_第3张图片
由于setMaxAge和setPath这两个方法比较重要,我还是想再解释一下这两个方法的意思。
Java Web基础入门第十八讲 Servlet开发——使用Cookie进行会话管理_第4张图片
最后,response接口中定义了一个addCookie方法,它用于在其响应头中增加一个相应的Set-Cookie头字段。同样,request接口中也定义了一个getCookies方法,它用于获取客户端提交的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("您上次访问时间是:");

		//获取浏览器访问服务器时传递过来的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时,效果如下所示:
Java Web基础入门第十八讲 Servlet开发——使用Cookie进行会话管理_第5张图片
点击浏览器的刷新按钮,进行第二次访问,此时服务器就可以通过Cookie获取浏览器上一次访问的时间了,效果如下:
Java Web基础入门第十八讲 Servlet开发——使用Cookie进行会话管理_第6张图片
在上面的例子中,在程序代码中并没有使用setMaxAge方法设置Cookie的有效期,所以当关闭浏览器之后,Cookie就失效了,要想在关闭了浏览器之后,Cookie依然有效,那么在创建Cookie时,就要为Cookie设置一个有效期。如下所示:
在这里插入图片描述
当第一次访问时这个Servlet时,效果如下所示:
Java Web基础入门第十八讲 Servlet开发——使用Cookie进行会话管理_第7张图片
用户第一次访问时,服务器发送给浏览器的Cookie就存储到了硬盘上,市面上有各种浏览器,那么我们又该如何通过这些浏览器查看保存的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中存取中文

要想在Cookie中存储中文,那么必须使用URLEncoder类里面的encode(String s, String enc)方法进行中文转码,例如:
在这里插入图片描述
在获取Cookie中的中文数据时,再使用URLDecoder类里面的decode(String s, String enc)进行解码,例如:
在这里插入图片描述

显示用户上次浏览过的商品

例,用户在上网购买商品时,通常会在网站的左侧显示用户上次浏览过的商品,如:
Java Web基础入门第十八讲 Servlet开发——使用Cookie进行会话管理_第8张图片
显示上次浏览商品的实现过程如下动图所示。
Java Web基础入门第十八讲 Servlet开发——使用Cookie进行会话管理_第9张图片
温馨提示,当用户第二次点击查看某个商品时,绝对不是回送一个新的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。在设计该类时需要注意以下几点。

  • 在Java里面,有两种类型的容器,一种是单列的容器(诸如List集合),一种是双列的容器(诸如Map集合)。要保存网站所有商品,是使用单列的容器还是使用双列的容器呢?很显然,要使用双列的容器。在实际开发时,有一个原则:有没有检索数据的需求。如果有检索数据的需求,则通通用双列的容器;如果没有检索数据的需求,则通通用单列的容器
  • 确定好使用双列的容器之后,还要希望存进去的顺序和取出来的顺序一致,那就不要用HashMap了,应该用LinkedHashMap(带链表的数据结构,可保证有序);
  • 并且希望Db这个类一初始化时,就向Map集合中初始化一些书。
//代表数据库
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)。此类在设计时应分为两部分:

  1. 输出网站所有商品;
  2. 显示用户曾经浏览过的商品。
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)。此类在设计时也应分为两部分:

  1. 据用户带过来的id,显示相应商品的详细信息;
  2. 构建Cookie,回写给浏览器。怎么构建Cookie呢?这是该需求的难点!构建Cookie应分类讨论(假设最近浏览过的商品列表的条目数最多为3条,即最多只能显示3条浏览记录)。
    Java Web基础入门第十八讲 Servlet开发——使用Cookie进行会话管理_第10张图片

在构建Cookie时,涉及到把数组变成List集合,此时用到Arrays工具类的1个常见方法:public static List asList(T... a),返回一个受指定数组支持的固定大小的列表。把数组变成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); } }

Cookie注意细节

Java Web基础入门第十八讲 Servlet开发——使用Cookie进行会话管理_第11张图片

你可能感兴趣的:(Java,Web基础入门)