会话及会话技术、Cookie对象、Session对象 详解
会话及会话技术
当用户通过浏览器访问Web应用时,通常情况下,服务器需要对用户的状态进行跟踪。例如,用户在网站结算商品时,Web服务器必须根据请求用户的身份,找到该用户所购买的商品。在Web开发中,服务器跟踪用户信息的技术称为会话技术,下面将针对会话及会话技术进行详细讲解。
1. 会话概述
在日常生活中,从拨通电话到挂断电话之间的一连串的你问我答的过程就是一个会话。在打电话过程中,通话双方会有通话内容,同样,在客户端与服务器交互的过程中,也会产生一些数据。例如,用户甲和乙分别登录了购物网站,甲购买了一个iPhone手机,乙购买了一个iPad,当这两个用户结账时,Web服务器需要对用户甲和乙的信息分别进行保存。为了保存会话过程中产生的数据,Servlet提供了两个用于保存会话数据的对象,分别是Cookie和Session。
2. Cookie对象
在现实生活中,当顾客在购物时,商城经常会赠送顾客一张会员卡,卡上记录用户的个人信息(姓名,手机号等)、消费额度和积分额度等。顾客一旦接受了会员卡,以后每次光临该商场时,都可以使用这张会员卡,商场也将根据会员卡上的消费记录计算会员的优惠额度和累加积分。在Web应用中,Cookie的功能类似于会员卡,当用户通过浏览器访问Web服务器时,服务器会给客户端发送一些信息,如用户信息和商品信息,这些信息都保存在Cookie中。这样,当该浏览器再次访问服务器时,会在请求头中将Cookie发送给服务器,方便服务器对浏览器做出正确地响应。服务器向客户端发送Cookie时,会在HTTP响应头字段中增加Set-Cookie响应头字段。Set-Cookie头字段中设置的Cookie的具体示例如下:Set-Cookie: user=itcast; Path=/;user表示Cookie的名称,itcast表示Cookie的值,Path表示Cookie的属性。Cookie必须以键值对的形式存在,Cookie属性可以有多个,属性之间用分号“;”和空格分隔。当用户第一次访问服务器时,服务器会在响应消息中增加Set-Cookie头字段,将用户信息以Cookie的形式发送给浏览器。一旦用户浏览器接受了服务器发送的Cookie信息,就会将它保存在浏览器的缓冲区中,这样,当浏览器后续访问该服务器时,都会在请求消息中将用户信息以Cookie的形式发送给服务器,从而使服务器分辨出当前请求是由哪个用户发出的。Cookie类有且仅有一个构造方法,具体语法格式如下:public Cookie(java.lang.String name,java.lang.String value);在Cookie的构造方法中,参数name用于指定Cookie的名称,value用于指定Cookie的值。需要注意的是,Cookie一旦创建,它的名称就不能再更改,Cookie的值可以为任何值,创建后允许被修改。
1. 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项是否只能使用安全的协议传送 |
2. setMaxAge(int expiry)方法和getMaxAge()方法
setMaxAge(int expiry)和getMaxAge()方法分别用于设置和返回Cookie在浏览器上保持有效的秒数。如果设置的值为一个正整数,浏览器会将Cookie信息保存在本地硬盘中。从当前时间开始,在没有超过指定的秒数之前,这个Cookie都保持有效,并且同一台计算机上运行的该浏览器都可以使用这个Cookie信息。如果设置值为负整数,浏览器会将Cookie信息保存在浏览器的缓存中,当浏览器关闭时,Cookie信息会被删除。如果设置值为0,则浏览器会立即删除这个Cookie信息。
3. setPath(String uri)方法和getPath()方法
setPath(String uri)方法和getPath()方法是针对Cookie的Path属性的。如果创建的某个Cookie对象没有设置Path属性,那么该Cookie只对当前访问路径所属的目录及其子目录有效。如果想让某个Cookie项对站点的所有目录下的访问路径都有效,应调用Cookie对象的setPath()方法将其Path属性设置为“/”。
4. setDomain(String pattern)方法和getDomain()方法
setDomain(String pattern)方法和getDomain()方法是针对Cookie的domain属性的。domain属性用于指定浏览器访问的域。例如,传智播客的域为“itcast.cn”。设置domain属性时,其值必须以“.”开头,如domain=.itcast.cn。默认情况下,domain属性的值为当前主机名,浏览器在访问当前主机下的资源时,都会将Cookie信息发送给服务器(当前主机)。需要注意的是,domain属性的值不区分大小写。
案例:显示用户上次访问时间
当用户访问某些Web应用时,经常会显示出该用户上一次的访问时间。例如,QQ登录成功后,会显示用户上次的登录时间。本案例要求使用Cookie技术实现显示用户上次的访问时间。显示用户上次访问时间效果如下图所示。
package com.miao;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;import java.io.IOException;import java.net.URLDecoder;import java.net.URLEncoder;import java.text.SimpleDateFormat;import java.util.Date;@WebServlet(name = "ServletDemo11",value = "/ServletDemo11")public class ServletDemo11 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 指定服务器输出内容的编码方式为UTF-8,防止发生乱码 resp.setContentType("text/html;charset=utf-8");// 获取所有的cookie Cookie[] cookies = req.getCookies();// 定义一个flag变量用于判断cookie是否为空 boolean flag = false;// 遍历cookies数组 if (cookies != null && cookies.length > 0) {// 定义一个字符串,用于保存 lastTime String lastTime = "lastTime"; for (Cookie cookie : cookies) {// 获取cookie的名称 String name = cookie.getName();// 判断是否为lastTime if (lastTime.equals(name)) {// 该cookie不是第一次登录,显示上次登录的时间 flag = true;// 获取cookie的value String value = cookie.getValue(); System.out.println(value); System.out.println("------------");// 解码 value = URLDecoder.decode(value, "utf-8"); System.out.println(value); resp.getWriter().write("欢迎回来 , 您上次访问的时间是: " + value);// 重新设置访问的时间 Date date = new Date(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年mm月dd日 HH:mm:ss"); String format = simpleDateFormat.format(date); System.out.println(format); System.out.println("-------------"); String encode = URLEncoder.encode(format,"utf-8"); System.out.println(encode);// 保存cookie的值 cookie.setValue(encode);// 设置cookie的存活时间 一个月 cookie.setMaxAge(60 * 60 * 24 * 30);// 加入当前cookie请求时间 resp.addCookie(cookie); break; } } if (cookies == null || cookies.length == 0 || flag == false) { Date date = new Date(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年mm月dd日 HH:mm:ss"); String format = simpleDateFormat.format(date); System.out.println(format); System.out.println("-------------"); String encode = URLEncoder.encode(format,"utf-8"); System.out.println(encode);// 创建一个cookie Cookie cookie = new Cookie(lastTime,encode);// 设置cookie的存活时间 一个月 cookie.setMaxAge(60 * 60 * 24 * 30);// 加入当前cookie请求时间 resp.addCookie(cookie); resp.getWriter().write("您好欢迎首次登陆!!!"); } } }}
刷新之后:
3. Session对象
当人们去医院就诊时,就诊病人需要办理医院的就诊卡,就诊卡上只有卡号,没有其他信息。但病人每次去该医院就诊时,只要出示就诊卡,医务人员便可根据卡号查询到病人的就诊信息。Session技术类似医院办理就诊卡和医院为每个病人保留病历档案的过程。当浏览器访问Web服务器时,Servlet容器就会创建一个Session对象和ID属性, Session对象就相当于病历档案,ID就相当于就诊卡号。当客户端后续访问服务器时,只要将ID传递给服务器,服务器就能判断出该请求是哪个客户端发送的,从而选择与之对应的Session对象为其服务。用户甲和用户乙都调用buyServlet将商品添加到购物车,调用payServlet进行商品结算。由于甲和乙购买商品的过程类似,在此,以用户甲为例进行详细说明。当用户甲访问购物网站时,服务器为甲创建了一个Session对象(相当于购物车)。当甲将iPhone手机添加到购物车时,iPhone手机的信息便存放到了Session对象中。同时,服务器将Session对象的ID属性以Cookie (Set-Cookie: JSESSIONID=111)的形式返回给甲的浏览器。当甲完成购物进行结账时,需要向服务器发送结账请求,这时,浏览器自动在请求消息头中将Cookie (Cookie: JSESSIONID=111)信息发送给服务器,服务器根据ID属性找到为用户甲所创建的Session对象,并将Session对象中所存放的iPhone手机信息取出进行结算。Session还具有更高的安全性,它将关键数据保存在服务器。cookie则是将数据存在客户端的浏览器中。因此cookie是较为危险的,若客户端遭遇黑客攻击,cookie信息容易被窃取,数据也可能被篡改,而运用Session可以有效避免这种情况的发生。
1. Session对象的getSession()方法
Session是与每个请求消息紧密相关的,为此,HttpServletRequest定义了用于获取Session对象的getSession()方法,该方法有两种重载形式,具体如下:public HttpSession getSession(boolean create)//第一个public HttpSession getSession()//第二个第一个getSession()方法根据传递的参数判断是否创建新的HttpSession对象,如果参数为true,则在相关的HttpSession对象不存在时创建并返回新的HttpSession对象,否则不创建新的HttpSession对象,而是返回null。第二个getSession()方法相当于第一个方法参数为true时的情况,在相关的HttpSession对象不存在时总是创建新的HttpSession对象。需要注意的是,由于getSession()方法可能会产生发送会话标识号的Cookie头字段,所以必须在发送任何响应内容之前调用getSession()方法。
2. 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 setAttribite(String name,Object value) | 用于将一个对象与一个名称关联后存储到当前的HttpSession对象中 |
String getAttribute() | 用于从当前HttpSession对象中返回指定名称的属性对象 |
void removeAttribute(String name) | 用于从当前HttpSession对象中删除指定名称的属性 |
3. Session生效
Sessinon在用户第一次访问服务器时创建,需要注意只有访问JSP(JSP将在第6章讲解)、Servlet等程序时才会创建Session。此外,还可调用request.getSession(true)强制生成Session。只访问HTML、IMAGE等静态资源并不会创建Session。
4. Session失效—“超时限制”判断Session是否生效
Web服务器采用“超时限制”判断客户端是否还在继续访问。在一定时间内,如果某个客户端一直没有请求访问,那么,Web服务器就会认为该客户端已经结束请求,并且将与该客户端会话所对应的HttpSession对象变成垃圾对象,等待垃圾收集器将其从内存中彻底清除。反之,如果浏览器超时后,再次向服务器发出请求访问,那么,Web服务器会创建一个新的HttpSession对象,并为其分配一个新的ID属性。
5. Session失效—强制Session失效
invalidate()方法,该方法可以强制使Session对象失效,具体用法如下所示:HttpSession session = request.getSession();session.invalidate();//注销该request的所有session有时默认的Session失效时间并不能满足我们的需求。这时我们需要自定义Session的失效时间,自定义Session的失效时间有3种:第1种在项目的web.xml文件中配置Session的失效时间,具体代码如下所示: 30需要注意的是,在web.xml配置的Session失效时间默认是分钟,所以上述代码设置Session失效时间为30分钟。第2种在Servlet程序中手动设置Session的失效时间,具体代码如下所示:session.setMaxInactiveInterval(30 * 60);//设置单位为秒,设置为-1永不过期第3种上述代码设置了Session的失效时间为30分钟,如果将值设置为-1,则该Session永不过期。在\conf\web.xml文件中,可以找到如下一段配置信息: 30在上述代码的配置信息中,设置的时间值是以分钟为单位的,即Tomcat服务器的默认会话超时间隔为30分钟。如果将元素中的时间值设置成0或负数,则表示会话永不超时。需要注意的是\conf\web.xml文件对站点内的所有Web应用程序都起作用。
案例:实现购物车功能
1.ServletDemo12 展示购物车中的商品
package com.miao;import com.miao.DB.CakeDB;import com.miao.pojo.Cake;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;/ * 展示所有可以购买的蛋糕的列表 */@WebServlet(name = "ServletDemo12", value = "/ServletDemo12")public class ServletDemo12 extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 设置响应的格式编码 resp.setContentType("text/html;charset=utf-8"); PrintWriter writer = resp.getWriter(); Collection<Cake> cakes = CakeDB.getAll();// 遍历输出cakes列表 writer.write("本店提供的蛋糕有:
" ); for (Cake cake : cakes) { String url = "PurchaseServlet?id=" + cake.getId(); writer.write(cake.getName() + "<a href=" + url + ">" +" 购买
"); } }}
2.PurchaseServlet 用于处理购买的操作
package com.miao;import com.miao.DB.CakeDB;import com.miao.pojo.Cake;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(name = "PurchaseServlet", value = "/PurchaseServlet")public class PurchaseServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String id = req.getParameter("id");// 重定向,如果id为空,重新跳转到ServletDemo12 if (id == null || id.equals("")) { String url = "/ServletDemo01_war_exploded/ServletDemo12"; resp.sendRedirect(url); return; } Cake cake = CakeDB.getCake(id);// 创建或者获得用户的session对象 HttpSession session = req.getSession();// 从session对象中过去用胡的购物车 List<Cake> cart = (List) session.getAttribute("cart"); if (cart == null) {// 首次购买,为用户创建一个购物车List模拟购物车 cart = new ArrayList<Cake>();// 将购物车存进session对象 session.setAttribute("cart",cart); }// 将cake放入session cart.add(cake);// 创建cookie存放session的标识号 Cookie cookie = new Cookie("JSESSIONID", session.getId());// 设置cookie的过期时间 cookie.setMaxAge(60 * 30);// 设置有效的路径 cookie.setPath("/");// 将cookie添加到相应体 resp.addCookie(cookie);// 重定向到购物车页面 String url = "/ServletDemo01_war_exploded/CartServlet"; resp.sendRedirect(url); }}
3.CartServlet 购物车展示
package com.miao;import com.miao.pojo.Cake;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.List;@WebServlet(name = "CartServlet", value = "/CartServlet")public class CartServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8"); PrintWriter writer = resp.getWriter();// 变量cart引用用的购物车 List<Cake> cart = null;// 标记用户是否购买过商品 boolean purFlag = true;// 获得用户的session HttpSession session = req.getSession();// 如果session为空,将purFlag设置为false if (session == null) { purFlag = false; } else {// 获得用户的购物车 cart = (List) session.getAttribute("cart");// 如果购物车为空 purFlag置为false if (cart == null) { purFlag = false; } } String url = "/ServletDemo01_war_exploded/ServletDemo12";//如果pueFlag为false,则表示用户没有购买商品,返回商品列表页面 if (purFlag == false) { writer.write("对不起,您没有购买任何商品!!! <a href=" + url + ">点击返回列表页面"); } else { writer.write("您购买的蛋糕有
"); for (Cake cake : cart) { writer.write(cake.getName() + "
"); } writer.write("<a href=" + url + ">点击返回列表页面,继续添加"); } }}
4.Cake实例
package com.miao.pojo;import java.io.Serializable;public class Cake implements Serializable { private static final long serialVersionUID = 1L; private String id; private String name; public Cake() { } public Cake(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; }}
5. CakeDB 模拟数据库
package com.miao.DB;import com.miao.pojo.Cake;import java.util.Collection;import java.util.LinkedHashMap;import java.util.Map;public class CakeDB { private static Map<String, Cake> cake = new LinkedHashMap<String, Cake>(); static { cake.put("1", new Cake("1", "A蛋糕")); cake.put("2", new Cake("2", "B蛋糕")); cake.put("3", new Cake("3", "C蛋糕")); cake.put("4", new Cake("4", "D蛋糕")); }// 获得所有的蛋糕 public static Collection<Cake> getAll(){ return cake.values(); } // 根据id获得指定的蛋糕 public static Cake getCake(String id) { return cake.get(id); }}
案例:实现用户登录注册功能
1 login.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body><form action="http://localhost:8080/ServletDemo01_war_exploded/LoginServlet" method="post"> username: <input type="text" name="username" id="username"> password: <input type="password" name="password" id="password"> <input type="submit" value="提交"></form></body></html>
2. LoginServlet 处理登录的操作
package com.miao;import com.miao.pojo.User;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;@WebServlet(name = "LoginServlet",value = "/LoginServlet")public class LoginServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8"); String username = req.getParameter("username"); String password = req.getParameter("password");// 模拟数据库的验证 if (username.equals("zhangsan") && password.equals("123456")) { User user = new User(); user.setUsername(username); user.setPassword(password); req.getSession().setAttribute("user", user); resp.sendRedirect("/ServletDemo01_war_exploded/IndexServlet"); } else { resp.sendRedirect("login.html"); } }}
3. IndexServlet 展示用户登录的结果
package com.miao;import com.miao.pojo.User;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.*;import java.io.IOException;@WebServlet(name = "IndexServlet",value = "/IndexServlet")public class IndexServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8");// 获取session HttpSession session = req.getSession(); User user = (User) session.getAttribute("user"); if (user == null) { String url = "login.html"; resp.getWriter().write("您还没有登录,请点击<a href=" + url + ">登录"); } else { String url = "LogoutServlet"; resp.getWriter().write("您已经登录,欢迎" + user.getUsername()); resp.getWriter().write("<a href=" +url +">退出 "); Cookie cookie = new Cookie("JSESSIONID", session.getId()); cookie.setMaxAge(60 * 30); cookie.setPath("/"); resp.addCookie(cookie); } }}
4. LogoutServlet 登出用户
package com.miao;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;@WebServlet(name = "LogoutServlet",value = "/LogoutServlet")public class LogoutServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8"); req.getSession().removeAttribute("user"); resp.sendRedirect("/ServletDemo01_war_exploded/IndexServlet"); }}