文章目录
当用户通过浏览器访问Web应用时,通常情况下,服务器需要对用户的状态进行跟踪,例如:用户在网站结算商品时,Web服务器必须根据请求用户的身份,找到该用户所购买的商品。在Web开发中,服务器跟踪用户信息的技术称为会话技术。为了保存会话过程中产生的数据,在Servlet技术中,提供了两个保存会话数据的对象,分别是 Cookie 和 Session。
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
第一次访问
第二次访问
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/>");
}
}
}
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 />");
}
}
}
}
实现用户登录
当用户访问某个网站的首页时,首先会判断用户是否登录,如果已经登录则在首页中显示用户登录信息,否则进入登录页面,完成用户登录功能,然后显示用户登录信息。在用户登录的情况下,如果单击用户登录界面中的 “退出” 时,就会注销当前用户的信息,返回首界面。
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. 启动项目,查看结果
直接访问首页
登录页面
登录失败后
点击返回后,输入正确的用户名和密码
点击退出
利用 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)效果展示
验证码输入错误后
验证码和密码同时输入正确