文章目录


当用户通过浏览器访问Web应用时,通常情况下,服务器需要对用户的状态进行跟踪,例如:用户在网站结算商品时,Web服务器必须根据请求用户的身份,找到该用户所购买的商品。在Web开发中,服务器跟踪用户信息的技术称为会话技术。为了保存会话过程中产生的数据,在Servlet技术中,提供了两个保存会话数据的对象,分别是 CookieSession

Cookie 对象

Cookie 是一种会话技术,它用于将会话过程中的数据保存到用户的浏览器中,从而使浏览器和服务器可以更好地进行数据交互。

在 Web 应用中,Cookie 的功能类似于会员卡,当用户通过浏览器访问 Web 应用时,服务器会给客户端发送一些信息,这些信息都保存在 Cookie 中。这样,当该浏览器再次访问服务器时,都会在请求头中将 Cookie 发送给服务器,方便服务器对浏览器做出正确的响应。

服务器向客户端发送 Cookie 时,会在 HTTP 响应头字段中增加 Set-Cookie 响应头字段。

​Set-Cookie字段格式如下​

Set-Cookie: user=itcast; Path=/;

Cookie API

为了封装 Cookie 信息,在 Servlet API 中提供了一个 javax.servlet.http.Cookie 类,该类包含了生成 Cookie 信息和提取 Cookie 信息各个属性的方法。

构造方法

在 Cookie 的构造方法中,参数 name 用于指定 Cookie 的名称,value 用于指定 Cookie 的值,Cookie 一旦创建,它的名称就不能更改,Cookie 的值可以为任何值,创建后允许被修改。

public Cookie(String name, String value)

Cookie 类的常用方法

方法声明

功能描述

String getName()

返回Cookie的名称

void setValue(String newValue)

为Cookie设置一个新的值

String getValue()

返回Cookie的值

void setMaxAge(int expiry)

设置Cookie在浏览器客户机上保持有效的秒数

int getMaxAge()

返回Cookie在浏览器客户机上保持有效的秒数

void setPath(String uri)

设置该Cookie项的有效目录路径

String getPath()

返回该Cookie项的有效目录路径

void setDomain(String pattern)

设置该Cookie项的有效域

String getDomain()

返回该Cookie项的有效域

void setVersion(int v)

设置该Cookie项采用的协议版本

int getVersion()

返回该Cookie项采用的协议版本

void setComment(String purpose)

设置该Cookie项的注解部分

String getComment()

返回该Cookie项的注解部分

void setSecure(boolean flag)

设置该Cookie项是否只能使用安全的协议传送

boolean getSecure()

返回该Cookie项是否只能使用安全的协议传送

获取 request 对象的所有 Cookie 信息

Cookie[] cookies = request.getCookies();

方法

说明

Cookie[] getCookies()

获取所有的Cookie对象,并存放在数组中

注意:Cookie 对象的 Max-Age 属性的值默认是 -1,即浏览器关闭时,删除这个 Cookie 对象。

实例

@WebServlet("/time")
public class LastAccessServlet extends HttpServlet {

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

resp.setContentType("text/html;charset=utf-8");

String lasttime = null;
// 获取所有的cookie,并存放在数组中
Cookie[] cookies = req.getCookies();
// 遍历cookie数组
for (int i = 0; i < cookies.length; i++) {
if("lastAccess".equals(cookies[i].getName())){
lasttime = cookies[i].getValue();
break;
}
}
// 判断是否存在名称为lastAccess的cookie
if(lasttime==null){
resp.getWriter().println("首次登录");
}else{
resp.getWriter().println("上次访问的时间是:" + lasttime);
}
// 创建cookie,将当前时间作为cookie的值发送给客户端
Cookie cookie = new Cookie(("lastAccess"), LocalDateTime.now().toString());
resp.addCookie(cookie);
}
}

​show​​​​第一次访问​

Servlet笔记三(会话及其会话技术)_Session


​第二次访问​

Servlet笔记三(会话及其会话技术)_Session_02

Session对象

Cookie 技术可以将用户的信息保存在各自的浏览器中,并且可以在多次请求下实现数据的共享。但是,如果传递的信息比较多,使用 Cookie 技术显然会增大服务器端程序处理的难度,这时,可以使用 Session 技术。Session 是一种将会话数据保存到服务器端的技术。

当浏览器访问 Web 服务器时,Servlet 容器就会创建一个 Session 对象(保存在服务器)和 ID 属性(返回给浏览器),当客户端后续访问服务器时,只要将标识号传递给服务器,服务器就能判断出该请求是哪个客户端发送的,从而选择与之对应的 Session 对象为其服务。
需要注意的是,由于客户端需要接收、记录和回送 Session 对象的 ID,因此,通常情况下,Session 是借助 Cookie 技术来传送 ID 属性的。

HttpSession API

HttpServletRequest 定义了用于获取 Session 对象的 getSession() 方法,该方法有两种重载形式:

public HttpSession getSession(boolean create)
public HttpSession getSession()

上面重载的两个方法都用于返回与当前请求相关的 HttpSession 对象。不同的是,第 1 个 getSession() 方法根据传递的参数来判断是否创建新的 HttpSession 对象,如果参数为 true,则在相关的 HttpSession 对象不存在时创建并返回新的 HttpSession 对象,否则不创建新的 HttpSession 对象,而是返回 null。第 2 个 getSession() 方法则相当于第 1 个方法参数为 true 时的情况,在相关的 HttpSession 对象不存在时总是创建新的 HttpSession 对象。

注意:由于 getSession() 方法可能会产生发送会话标识号的 Cookie 头字段,因此,必须在发送任何响应内容之前调用 getSession() 方法。

HttpSession 接口中的常用方法

方法声明

功能描述

String getId()

用于返回与当前HttpSession对象关联的会话标识号

long getCreationTime()

返回Session创建的时间,这个时间是创建Session的时间与1970年1月1日00:00:00之间的时间差的毫秒表示形式

long getLastAccessedTime()

返回客户端最后一次发送与Session相关请求的时间,这个时间是发送请求的时间与1970年1月1日00:00:00之间的时间差的毫秒表示形式

void setMaxInactiveInterval(int interval)

用于设置当前HttpSession对象可空闲的以秒为单位的最长时间,也就是修改当前会话默认超时间隔

boolean isNew()

判断当前HttpSession对象是否是新创建的

void invalidate()

用于强制使Session对象无效

ServletContext getServletContext()

用于返回当前HttpSession对象所属于的Web应用程序对象,即代表当前Web应用程序的ServletContext对象

void setAttribute(String name, Object value)

用于将一个对象与一个名称关联后存储到当前的HttpSession对象中

String getAttribute(String name)

用于从当前HttpSession对象中返回指定名称的属性对象

void removeAttribute(String name)

用于从当前HttpSession对象中删除指定名称的属性

Session超时管理

在一定时间内,如果某个客户端一直没有请求访问,那么,Web 服务器就会认为该客户端已经结束请求,并且将与该客户端会话对应的 HttpSession 对象变成垃圾对象,等待垃圾收集器将其从内存中清除。反之,如果浏览器超时后,再次向服务器发出请求访问,那么,Web 服务器则会创建一个新的 HttpSession 对象,并为其分配一个新的 ID 属性。

在会话过程中,会话的有效时间可以在 web.xml 文件中设置,其默认值由 Servlet 容器定义。

<session-config>
<session-timeout>30</session-timeout>
</session-config>

在上述配置信息中,配置的时间值是以分钟为单位的,如果 <session-timeout> 元素中的时间值设置成 0 或一个负数,则表示会话永不超时。

Session 对象的两个应用实例

实现购物车

当用户使用浏览器访问某个网站的图书列表页面时,如果购买某一本书,那么首先会判断书籍是否存在,如果存在就加入购物车,跳转到购物车中所购买的列表页面。否则,返回图书列表页面。

1. 用于创建封装图书信息类

import java.io.Serializable;

// 用于封装图书信息
public class Book implements Serializable {

private static final long serialVersionUID = 1L;
private String id;
private String name;

public Book(){
}

public Book(String id, String name){
this.id = id;
this.name = name;
}

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

2. 创建数据库模拟类

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

// 用于模拟保存所有图书的数据库
public class BookDB {

private static Map<String, Book> books = new LinkedHashMap<>();

static {
books.put("1", new Book("1", "JAVA基础"));
books.put("2", new Book("2", "JAVAWEB开发"));
books.put("3", new Book("3", "HTML"));
books.put("4", new Book("4", "PYTHON"));
books.put("5", new Book("5", "C++"));
}

// 获取所有图书对象
public static Collection<Book> getAll(){
return books.values();
}

// 根据指定的id获得图书对象
public static Book getBook(String id){
return books.get(id);
}
}

3. 创建Servlet

1)创建一个名称为 ListServlet 的 Servlet 类,该 Servlet 用于显示所有可购买图书的列表,通过单击 “购买” 链接,便可将指定的图书添加到购物车中。

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 java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;

// 该Servlet用于显示所有可购买图书的列表,通过单击"购买"链接,便可将指定的图书添加到购物车中
@WebServlet("/ListServlet")
public class ListBookServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
Collection<Book> books = BookDB.getAll();
out.write("本站提供的图书有:<br />");
for (Book book: books){
String url = "/PurchaseServlet?id=" + book.getId();
out.write(book.getName() + "<a href='" + url + "'>点击购买</a><br/>");
}
}
}

Servlet笔记三(会话及其会话技术)_Cookie_03


2)创建一个名称为 PurchaseServlet 的 Servlet 类,实现了两个功能,一个是将用户购买的图书信息保存到 Session 对象中;一个实在用户购买图书结束后,将页面重定向到用户已经购买的图书列表。该类在实现时,通过 ArrayList 集合模拟了一个购物车,然后将购买的所有图书添加到购物车中,最后通过 Session 对象传递给 CartServlet,由 CartServlet 展示用户已经购买的图书。

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@WebServlet("/PurchaseServlet")
public class PurchaseServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

// 获得用户购买的商品
String id = req.getParameter("id");
if(id == null){
// 如果id为null,重定向到ListBookServlet页面
String url = "/ListServlet";
resp.sendRedirect(url);
return;
}
Book book = BookDB.getBook(id);
// 创建或者获得用户的Session对象
HttpSession session = req.getSession();
// 从Session对象中获得用户的购物车
List<Book> cart = (List)session.getAttribute("cart");
if(cart==null){
// 首次购买,为用户创建一个购物车(List集合模拟购物车)
cart = new ArrayList<>();
session.setAttribute("cart", cart);
}
// 将商品放入购物车
cart.add(book);
System.out.println(book.getName());
// 创建Cookie存放Session的标识号
Cookie cookie = new Cookie("JSESSIONID", session.getId());
cookie.setMaxAge(60 * 30); // 60s * 30
cookie.setPath("/");
resp.addCookie(cookie);
// 重定向到购物车页面
String url = "/CartServlet";
resp.sendRedirect(url);
}
}

3)创建一个名称为 CartServlet 的 Servlet 类,该类用于展示用户已经购买的图书列表

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;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

@WebServlet("/CartServlet")
public class CartServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
// 变量cart引用用户的购物车
List<Book> cart = null;
// List<Book> cart = new ArrayList<>();
// 标记用户是否买过商品
boolean purFlag = true;
// 获取session
HttpSession session = req.getSession(false);
if(session == null){
purFlag = false;
}else{
// 获得用户购物车
cart = (List)session.getAttribute("cart");
// 没用过购物车
if (cart == null){
purFlag = false;
}
}
if(!purFlag){
out.write("对不起!您还没有购买任何商品! <br />");
out.write("<a href='/ListServlet'>点击返回</a>");
}else{
out.write("您购买的图书有:<br />");
for (Book book: cart) {
out.write(book.getName() + "<br />");
}
}
}
}

Servlet笔记三(会话及其会话技术)_Session_04


Servlet笔记三(会话及其会话技术)_Servlet_05

实现用户登录

当用户访问某个网站的首页时,首先会判断用户是否登录,如果已经登录则在首页中显示用户登录信息,否则进入登录页面,完成用户登录功能,然后显示用户登录信息。在用户登录的情况下,如果单击用户登录界面中的 “退出” 时,就会注销当前用户的信息,返回首界面。

1. 创建封装用户的信息类

public class User {

private String username;
private String password;

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}

2. 创建Servlet

该 Servlet 用于显示网站的首页,如果用户没有登录,那么首界面会提示用户登录,否则,显示用户已经登录的信息。为了判断用户是否登录,该类在实现时,获取了保存用户信息的 Session 对象。

@WebServlet("/index")
public class IndexServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
HttpSession session = req.getSession();
User user = (User)session.getAttribute("user");

if(user == null){
out.println("您还没有登录, 请 <a href='/login.html'>登录</a>");
}else{
out.println("欢迎您," + user.getUsername() + "<br />");
out.println("<a href='/LogoutServlet'>退出</a>");
Cookie cookie = new Cookie("JSESSIONID", session.getId());
cookie.setPath("/");
cookie.setMaxAge(60 * 30);
resp.addCookie(cookie);
}
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}

该 Servlet 用于显示用户登录成功后的界面,如果用户登录成功,则跳转到网站首页,否则,则提示给用户登录失败。

@WebServlet("/login")
public class LoginServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

resp.setContentType("text/html;charset=utf-8");
String username = req.getParameter("username");
String password = req.getParameter("password");

PrintWriter out = resp.getWriter();
System.out.println(username + password);
if("admin".equals(username) && "123".equals(password)){

User user = new User();
user.setUsername(username);
user.setPassword(password);

HttpSession session = req.getSession();
session.setAttribute("user", user);

// Cookie cookie = new Cookie("JSESSIONID", session.getId());
// cookie.setMaxAge(60 * 30);
// cookie.setPath("/");
// resp.addCookie(cookie);
resp.sendRedirect("/index");

}else{
out.write("用户名或密码错误,登录失败<br/>");
out.println("<a href='/login.html'>返回</a>");
}

}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}

该 Servlet 用于完成用户注销功能,当用户单击 退出 时,该类会将 Session 对象中的用户信息移除,并跳转到网站的首界面。

@WebServlet("/LogoutServlet")
public class LogoutServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

req.getSession().removeAttribute("user");
resp.sendRedirect("/index");
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}

3. 创建登录页面

​login.html​​,该页面中包含用户登录表单信息

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="/login" method="post">
用户名:<input type="text" name="username" /><br />
密码:<input type="password" name="password"/><br />
<input type="submit" value="提交"/>
</form>
</body>
</html>

4. 启动项目,查看结果

直接访问首页

Servlet笔记三(会话及其会话技术)_Session_06

登录页面

Servlet笔记三(会话及其会话技术)_Servlet_07

登录失败后

Servlet笔记三(会话及其会话技术)_Servlet_08


点击返回后,输入正确的用户名和密码

Servlet笔记三(会话及其会话技术)_Session_09


点击退出

Servlet笔记三(会话及其会话技术)_Session_10

利用 Session 实现一次性验证码

一次性验证码的功能同样可以使用 Session 来实现,这里要实现的验证码是 4 个随机字符,需要对上面的第二个应用案例进行改写,增加一次性验证码的实现。

1)修改表单页面 login.html,增加验证码的输入框和验证码图片,其中验证码的图片来自 CheckServlet 类。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="/login" method="post">
用户名:<input type="text" name="username" /><br />
密码:<input type="password" name="password"/><br />
验证码:<input type="text" name="check_code"/>
<img src="/CheckServlet" /><br />
<input type="submit" value="提交"/>
</form>
</body>
</html>

2)编写 CheckServlet 类,用于产生验证码图片

package com.lz.jiaotong.wanghengbo;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class CheckServlet extends HttpServlet {

private static int WIDTH = 60;
private static int HEIGHT = 20;

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

HttpSession session = req.getSession();
resp.setContentType("image/jpeg");

ServletOutputStream sos = resp.getOutputStream();

// 设置浏览器不缓存此图片
resp.setHeader("Pragma", "No-cache");
resp.setHeader("Cache-Control", "non-cache");
resp.setDateHeader("Expires", 0);

// 创建内存图形并获得其图形上下文
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();

// 产生随机的验证码
char[] rands = generateCheckCode();
// 产生图像
drawBackground(g);
drawRands(g, rands);
// 结束图像的绘制,完成图像
g.dispose();
// 将图像输出到客户端
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ImageIO.write(image, "JPEG", bos);
byte[] buf = bos.toByteArray();
resp.setContentLength(buf.length);

sos.write(buf);

bos.close();
sos.close();
// 将当前验证码存入到Session中
session.setAttribute("check_code", new String(rands));

}

// 生成一个4字符的验证码
private char[] generateCheckCode(){
String chars = "0123456789abcdefghigklmnopqrstuvwxyz";
char[] rands = new char[4];
for (int i = 0; i < 4; i++) {
int rand = (int)(Math.random() * 36);
rands[i] = chars.charAt(rand);
}
return rands;
}

private void drawRands(Graphics g, char[] rands){
g.setColor(Color.BLACK);
g.setFont(new Font(null, Font.ITALIC|Font.BOLD, 18));
// 在不同高度上输出验证码的每个字符
g.drawString("" + rands[0], 1, 17);
g.drawString("" + rands[1], 16, 15);
g.drawString("" + rands[2], 31, 18);
g.drawString("" + rands[3], 46, 16);
System.out.println(rands);
}

private void drawBackground(Graphics g){
// 画背景
g.setColor(new Color(0xDCDCDC));
g.fillRect(0, 0, WIDTH, HEIGHT);
// 随机产生120个干扰点
for (int i = 0; i < 120; i++) {
int x = (int)(Math.random() * WIDTH);
int y = (int)(Math.random() * HEIGHT);
int red = (int)(Math.random() * 255);
int green = (int)(Math.random() * 255);
int blue = (int)(Math.random() * 255);
g.setColor(new Color(red, green, blue));
g.drawOval(x, y, 1, 0);
}
}
}

3)对 LoginServlet 类进行修改,增加对验证码的判断。

@WebServlet("/login")
public class LoginServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

resp.setContentType("text/html;charset=utf-8");

PrintWriter out = resp.getWriter();

String username = req.getParameter("username");
String password = req.getParameter("password");
String checkCode = req.getParameter("check_code");
String savedCode = (String)req.getSession().getAttribute("check_code");

if("admin".equals(username) && "123".equals(password)
&& checkCode!=null && checkCode.equals(savedCode)){

User user = new User();
user.setUsername(username);
user.setPassword(password);

HttpSession session = req.getSession();
session.setAttribute("user", user);
resp.sendRedirect("/index");

}else if(checkCode!=null && checkCode.equals(savedCode)){
out.write("用户名或密码错误,登录失败<br/>");
out.println("<a href='/login.html'>返回</a>");
}else{
out.println("验证码错误");
}
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}

4)效果展示

Servlet笔记三(会话及其会话技术)_java_11


验证码输入错误后

Servlet笔记三(会话及其会话技术)_Session_12


验证码和密码同时输入正确

Servlet笔记三(会话及其会话技术)_Servlet_13