第5章 会话及会话技术


学习目标

1. 会话概述

在日常生活中,客户向服务台从拨通电话到挂断电话之间的一连串的你问我答的过程我们可以认为是一次电话通话(一问一答之间间隔假如不超过30秒,若超过30秒服务台就会挂断电话);多次电话通话我们可认为是一个电话会话(多次电话通话之间间隔空隙假如不超过3小时,若超过3小时服务台就会结束本次电话会话)。

在客户端(浏览器)和服务器交互中,客户端向服务器从接通到断开连接之间的一连串的请求/响应的过程我们可以认为是一次网络连接交互(多次请求之间间隔假如不超过3秒,若超过3秒服务器就会断开连接----HTTP1.1);多次连接交互我们可认为是一个网络会话(多次网络连接交互之间间隔假如不超过30分钟,若超过30分钟服务器就会结束本次网络会话)。

在Web开发中,服务器跟踪用户客户端多次网络连接(多个请求/响应,3秒延时)的技术称为会话技术。Java Web的Servlet提供了两个用于保存会话数据的对象,分别是Cookie和Session。

Web应用程序是使用HTTP协议传输数据的。HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话

你可能会有这样的经历,登陆一个网站的时候会提醒你要不要记住账户和密码,这样下次来你就不用再次输入账号密码了。这就是cookie的作用,当我们再次访问的时候,方便服务器直接根据我们的cookie来直接取上一次取过的东西(对于每一个cookie服务器会对这个cookie存储上一次我们拿过的数据,下一次对于同一个cookie的时候,就直接在这里取)。

cookie通常是服务器保存在浏览器的一小段信息,每个cookie都不能超过4kb。浏览器每次向服务器发出请求时,就会自动在请求头附上所属域名、路径且未到期的这些Cookie的名称和值

cookie会保存以下几方面的信息:

参照ch04创建ch05_zzh项目,创建ch05首页index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <meta charset="UTF-8">
    <title>ch05 会话及会话技术(郑佐汉)</title>
  </head>
  <body>
  <h1>ch05 会话及会话技术(郑佐汉)</h1>
  <hr>
  <a href="html/CookieDemo.html">0. 浏览器端js设置、获取、删除Cookie【document.cookie=; =document.cookie; document.cookie= expires 】</a><br>
  <a href="cookie/cookieDemo1">1. 服务器端向客户端浏览器添加Cookie【Cookie c1= new Cookie(...);c1.set...;resp.addCookie(c1);】</a><br>
  <a href="cookie/cookieDemo2">2. 服务器端获取客户端本次请求携带指定名称的Cookie的值【Cookie[] cookies = req.getCookies(); 遍历cookies查找指定名称】</a><br>
  <a href="cookieDemo3">3. 服务器端获取客户端本次请求(有效路径/)携带所有的cookie【Cookie[] cookies = req.getCookies(); 遍历cookies】</a><br>
  <a href="cookie/cookieDemo4">4. 服务器端获取客户端(有效路径:/,/cookie及其子目录)本次请求携带所有的cookie【Cookie[] cookies = req.getCookies(); 遍历cookies】</a><br>
  <a href="xxx/aaa/bbb/cookieDemo5">5. 服务器端获取客户端(有效路径:/,/xxx/aaa/bbb及其子目录)本次请求携带所有的cookie【Cookie[] cookies = req.getCookies(); 遍历cookies】</a><br>
  <a href="html/cookieDemo6">6. 服务器端获取客户端(有效路径:/,/html及其子目录)本次请求携带所有的cookie【Cookie[] cookies = req.getCookies(); 遍历cookies】</a><br>
  <a href="cookie/lastTime">7. 上次访问时间【Cookie cookie = new Cookie("lastTime", System.currentTimeMillis() + "");)】</a><br>
  <a href="session/sessionDemo1">8. 获取session信息1【HttpSession session = req.getSession();】</a><br>
  <a href="session/sessionDemo2">  获取session信息2【HttpSession session = req.getSession();session.invalidate();】</a><br>
  <a href="session/sessionDemo3">  获取session信息3【HttpSession session = req.getSession();session.setMaxInactiveInterval(10);】</a><br>
  <a href="listCakeServlet">9. 购物车【session.getAttribute("cart");】</a><br>
  <a href="indexServlet">10. 用户登录【req.getSession().setAttribute("user", user);resp.sendRedirect("indexServlet")】</a><br>
  <hr>
  <p><a href="http://101.42.158.247/21javaweb.html">返回课程首页</a>
  郑佐汉 <script>document.write(document.lastModified); </script> 制作</p>
  </body>
</html>

运行结果如下:

图1 ch05 会话及会话技术--首页

浏览器可以设置不接受cookie,也可以设置接收服务器添加的cookie。一般来说cookie是由服务器添加,但浏览器也可执行js代码完成cookie的设置(添加、修改)、提取、删除(设置为过期)。

代码 说明
window.navigator.cookieEnabeled 返回一个布尔值,表示浏览器是否打开Cookie功能。
document.cookie 返回当前网页的是由分号空格分隔的所有cookie。
document.cookie = '名字=值[; 属性名=属性值'...]; 添加新的cookie;替换已有的同名、同有效域名、同有效路径的cookie;不是覆盖所有的cookie.

web\html\CookieDemo.html

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>浏览器端js设置、获取、删除Cookie</title>
</head>
<body>
<h2>浏览器端js设置、获取、删除Cookie(郑佐汉)</h2>
<hr/>
浏览器是否支持Cookie功能:<script>document.write(window.navigator.cookieEnabled); </script>
<hr/>

<form>
    <fieldset>
        <input type="button" value="设置cookie:用户名5,郑佐汉5,-1秒--浏览器关闭" onclick="setCookie('用户名5','郑佐汉5');">
        <input type="button" value="设置cookie:用户名6,郑佐汉6,50秒--有效生命" onclick="setCookie('用户名6','郑佐汉6',50);"><br>
        <input type="button" value="读取cookie:用户名5" onclick="document.getElementById('result').innerHTML = getCookie('用户名5')">
        <input type="button" value="读取cookie:用户名6" onclick="document.getElementById('result').innerHTML = getCookie('用户名6')">
        <input type="button" value="删除cookie:用户名5" onclick="delCookie('用户名5')">
        <input type="button" value="删除cookie:用户名6" onclick="delCookie('用户名6')"><br>
        <hr>
        <input type="button" value="设置cookie:username7,郑佐汉7,-1秒--浏览器关闭" onclick="cookieSet('username7','郑佐汉7');">
        <input type="button" value="设置cookie:username8,郑佐汉8,50秒--有效生命" onclick="cookieSet('username8','郑佐汉8',50);"><br>
        <input type="button" value="读取cookie:username7" onclick="document.getElementById('result').innerHTML = cookieGet('username7')">
        <input type="button" value="读取cookie:username8" onclick="document.getElementById('result').innerHTML = cookieGet('username8')">
        <input type="button" value="删除cookie:username7" onclick="document.getElementById('result').innerHTML = cookieDel('username7')">
        <input type="button" value="删除cookie:username8" onclick="document.getElementById('result').innerHTML = cookieDel('username8')"><br>
        <hr>
        <input type="button" value="读取所有有效的Cookie原始值" onclick="document.getElementById('result').innerHTML = getCookieAll();">
        <input type="button" value="遍历所有有效的Cookie" onclick="document.getElementById('result').innerHTML = cookieGetAll();">
    </fieldset>
</form>
<hr>运行结果:<span id="result"></span>

<script>
    function setCookie(name,value,MaxAge,domain,path,secure) {
        var exp = new Date();
        if(MaxAge > 0)
            exp.setTime(exp.getTime() + MaxAge * 1000);
        document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) +
            ((MaxAge > 0) ? "; expires=" + exp.toGMTString():"") +
            ((path) ? "; path=" + path : "") +
            ((domain) ? "; domain=" + domain : "") +
            ((secure) ? "; secure" : "");
    }

    function getCookie(name){
        var dc = document.cookie;
        var prefix = encodeURIComponent(name) + "=";
        var begin = dc.indexOf("; " + prefix);
        if (begin === -1) {
            begin = dc.indexOf(prefix);
            if (begin !== 0) return null;
        } else {
            begin += 2;
        }
        var end = document.cookie.indexOf(";", begin);
        if (end === -1) {
            end = dc.length;
        }
        return decodeURIComponent(dc.substring(begin + prefix.length, end));
    }

    function delCookie(name, domain, path) {
        if (getCookie(name)){
            document.cookie = encodeURIComponent(name) + "=" +
                ((path) ? "; path=" + path : "") +
                ((domain) ? "; domain=" + domain : "") +
                "; expires=Thu, 01-Jan-1970 00:00:01 GMT";
        }
    }

    // 简易方法
    cookieSet = function(name,val,MaxAge){
            var date=new Date(); //获取当前时间
            date.setTime(date.getTime()+MaxAge*1000); //格式化为cookie识别的时间
            if(MaxAge)
                document.cookie=name + "=" + encodeURIComponent(val) +";expires="+date.toGMTString(); //设置cookie
            else
                document.cookie=name + "=" + encodeURIComponent(val)//设置cookie
        }

    cookieGet = function(name){//获取cookie方法
        /*获取cookie参数*/
        var getCookie = document.cookie; //获取cookie
        var arrCookie = getCookie.split("; ")
        var tips;
        for(var i=0;i<arrCookie.length;i++){
            var arr=arrCookie[i].split("=");
            if(encodeURIComponent(name)===arr[0]){
                tips=decodeURIComponent(arr[1]);
                break;
            }
        }
        return tips;
    }

    getCookieAll = function(){//获取所有当前访问域名、路径的原始cookie名称和值
        return document.cookie;
    }

    cookieGetAll = function(){//获取所有当前访问域名、路径的cookie名称和值
        var getCookie = document.cookie; //获取cookie
        var arrCookie = getCookie.split("; ")
        var strCookies = "<br>";
        for(var i=0;i<arrCookie.length;i++){
            var arr=arrCookie[i].split("=");
            strCookies = strCookies + decodeURIComponent(arr[0]) +" = " + decodeURIComponent(arr[1]) + "<br>";
        }
        return strCookies;
    }

    cookieDel = function(name) {
        /*获取cookie参数*/
        var getCookie = document.cookie; //获取cookie
        var arrCookie = getCookie.split("; ")
        var tips=false;
        for(var i=0;i<arrCookie.length;i++){
            var arr=arrCookie[i].split("=");
            if(name===arr[0]){
                document.cookie = name + "=; expires=Thu, 01-Jan-1970 00:00:01 GMT";
                tips=true;
                break;
            }
        }
        return tips;
    }

</script>
</body>
</html>

运行结果如下:

图2 浏览器端js设置、获取、删除Cookie

服务器向客户端发送Cookie时,会在HTTP响应头字段中增加Set-Cookie响应头字段。Set-Cookie头字段中设置的Cookie的具体示例如下:分隔符 Set-Cookie是 冒号+空格;Cookie之间是 分号+空格

Set-Cookie: Cookie名=Cookie值; 属性名1=属性值1; 属性名2=属性值2; ...
图3 服务器设置Cookie的流程

javax.servlet.http.Cookie.java

package javax.servlet.http;

import java.io.Serializable;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.MessageFormat;
import java.util.BitSet;
import java.util.Locale;
import java.util.ResourceBundle;

public class Cookie implements Cloneable, Serializable {

    private static final CookieNameValidator validation;

    static {
        boolean strictServletCompliance;
        boolean strictNaming;
        boolean allowSlash;
        String propStrictNaming;
        String propFwdSlashIsSeparator;

        if (System.getSecurityManager() == null) {
            strictServletCompliance = Boolean.getBoolean(
                    "org.apache.catalina.STRICT_SERVLET_COMPLIANCE");
            propStrictNaming = System.getProperty(
                    "org.apache.tomcat.util.http.ServerCookie.STRICT_NAMING");
            propFwdSlashIsSeparator = System.getProperty(
                    "org.apache.tomcat.util.http.ServerCookie.FWD_SLASH_IS_SEPARATOR");
        } else {
            strictServletCompliance = AccessController.doPrivileged(
                    (PrivilegedAction<Boolean>) () -> Boolean.valueOf(System.getProperty(
                            "org.apache.catalina.STRICT_SERVLET_COMPLIANCE"))).booleanValue();
            propStrictNaming = AccessController.doPrivileged(
                    (PrivilegedAction<String>) () -> System.getProperty(
                            "org.apache.tomcat.util.http.ServerCookie.STRICT_NAMING"));
            propFwdSlashIsSeparator = AccessController.doPrivileged(
                    (PrivilegedAction<String>) () -> System.getProperty(
                            "org.apache.tomcat.util.http.ServerCookie.FWD_SLASH_IS_SEPARATOR"));
        }

        if (propStrictNaming == null) {
            strictNaming = strictServletCompliance;
        } else {
            strictNaming = Boolean.parseBoolean(propStrictNaming);
        }

        if (propFwdSlashIsSeparator == null) {
            allowSlash = !strictServletCompliance;
        } else {
            allowSlash = !Boolean.parseBoolean(propFwdSlashIsSeparator);
        }

        if (strictNaming) {
            validation = new RFC2109Validator(allowSlash);
        } else {
            validation = new RFC6265Validator();
        }
    }

    private static final long serialVersionUID = 1L;

    private final String name;
    private String value;
    private int maxAge = -1;
    private String domain;
    private String path;
    private int version = 0;
    private String comment;
    private boolean secure;
    private boolean httpOnly;

    public Cookie(String name, String value) {
        validation.validate(name);
        this.name = name;
        this.value = value;
    }

    public void setComment(String purpose) {
        comment = purpose;
    }
    public String getComment() {
        return comment;
    }

    public void setDomain(String pattern) {
        domain = pattern.toLowerCase(Locale.ENGLISH); // IE allegedly needs this
    }
    public String getDomain() {
        return domain;
    }

    public void setMaxAge(int expiry) {
        maxAge = expiry;
    }
    public int getMaxAge() {
        return maxAge;
    }

    public void setPath(String uri) {
        path = uri;
    }
    public String getPath() {
        return path;
    }

    public void setSecure(boolean flag) {
        secure = flag;
    }
    public boolean getSecure() {
        return secure;
    }

    public String getName() {
        return name;
    }

    public void setValue(String newValue) {
        value = newValue;
    }
    public String getValue() {
        return value;
    }

    public int getVersion() {
        return version;
    }
    public void setVersion(int v) {
        version = v;
    }

    @Override
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }

    public void setHttpOnly(boolean httpOnly) {
        this.httpOnly = httpOnly;
    }
    public boolean isHttpOnly() {
        return httpOnly;
    }
}


class CookieNameValidator {
    private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
    protected static final ResourceBundle lStrings = ResourceBundle.getBundle(LSTRING_FILE);

    protected final BitSet allowed;

    protected CookieNameValidator(String separators) {
        allowed = new BitSet(128);
        allowed.set(0x20, 0x7f); // any CHAR except CTLs or separators
        for (int i = 0; i < separators.length(); i++) {
            char ch = separators.charAt(i);
            allowed.clear(ch);
        }
    }

    void validate(String name) {
        if (name == null || name.length() == 0) {
            throw new IllegalArgumentException(lStrings.getString("err.cookie_name_blank"));
        }
        if (!isToken(name)) {
            String errMsg = lStrings.getString("err.cookie_name_is_token");
            throw new IllegalArgumentException(MessageFormat.format(errMsg, name));
        }
    }

    private boolean isToken(String possibleToken) {
        int len = possibleToken.length();

        for (int i = 0; i < len; i++) {
            char c = possibleToken.charAt(i);
            if (!allowed.get(c)) {
                return false;
            }
        }
        return true;
    }
}

class RFC6265Validator extends CookieNameValidator {
    private static final String RFC2616_SEPARATORS = "()<>@,;:\\\"/[]?={} \t";

    RFC6265Validator() {
        super(RFC2616_SEPARATORS);
    }
}

class RFC2109Validator extends RFC6265Validator {
    RFC2109Validator(boolean allowSlash) {
        // special treatment to allow for FWD_SLASH_IS_SEPARATOR property
        if (allowSlash) {
            allowed.set('/');
        }
    }

    @Override
    void validate(String name) {
        super.validate(name);
        if (name.charAt(0) == '$') {
            String errMsg = lStrings.getString("err.cookie_name_is_token");
            throw new IllegalArgumentException(MessageFormat.format(errMsg, name));
        }
    }
}

(1) 构造方法

Cookie类有且仅有一个构造方法,具体语法格式如下:

public Cookie(java.lang.String name,java.lang.String value);

注意,Cookie一旦创建,它的名称就不能再更改,且构造Cookie时name不能取中文名称(js设置Cookie时名称可以取中文名称),Cookie的值可以为任何值,创建后允许被修改。

服务器向浏览器发送cookie的时候,除了cookie本身的内容,还有一些可选内容,它们都必须以分号开头。

方法声明 功能描述
String getName() 用于返回Cookie的名称
void setValue(String newValue) 用于为Cookie设置一个新的值
String getValue() 用于返回Cookie的值
void setDomain(String pattern) 用于设置该Cookie项的有效域
String getDomain() 用于返回该Cookie项的有效域
void setPath(String uri) 用于设置该Cookie项的有效目录路径
String getPath() 用于返回该Cookie项的有效目录路径
void setMaxAge(int expiry) 用于设置Cookie在浏览器客户机上保持有效的秒数
int getMaxAge() 用于返回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项是否只能使用安全的协议传送
图4 Cookie的限制

同源政策

浏览器的同源政策规定,两个网址只要有效域名(含端口)相同和有效路径相同,就可以共享Cookie。

  1. 识别用户身份
  2. 记录用户的操作历史

cn.zzh.cookie.CookieDemo1_zzh.java

package cn.zzh.cookie;

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;

@WebServlet("/cookie/cookieDemo1")
public class CookieDemo1_zzh extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //创建cookie
        Cookie c1 = new Cookie("username1", "zzh1");
        Cookie c2 = new Cookie("username2", URLEncoder.encode( "郑佐汉2","utf-8"));
        Cookie c3 = new Cookie("username3", URLEncoder.encode( "郑佐汉3","utf-8"));

        //设置 cookie的有效存活时间
        /*
        *默认cookie存活的时间是一次浏览器关闭
        *如果设置了存活时间,cookie的存活时间就是设置的有效秒数
        **/
        c1.setMaxAge(15);
        c1.setComment("username1,全拼音用户名");

        //设置 cookie的有效路径,/ 站点(服务器)的所有应用、的所有目录下的访问路径都有效
        c2.setPath("/");
        c2.setComment("username2,汉字用户名");
        c2.setHttpOnly(true);
        //c1,c3 缺省、默认有效路径:当前路径 /cookie

        System.out.println(c1.getName() + " = " + c1.getValue());
        System.out.println("c1 有效域   Domain=" + c1.getDomain());
        System.out.println("c1 有效路径 Path  =" + c1.getPath());
        System.out.println("c1 有效寿命 MaxAge=" + c1.getMaxAge()+"秒");
        System.out.println("c1 只http传送 HttpOnly=" + c1.isHttpOnly());
        System.out.println("c1 只https传送 Secure=" + c1.getSecure());
        System.out.println("c1 版本 Version=" + c1.getVersion());
        System.out.println("c1 注释 Comment=" + c1.getComment());

        System.out.println("\n" +c2.getName() + " = " + URLDecoder.decode(c2.getValue(),"utf-8"));
        System.out.println("c2 有效域   Domain=" + c2.getDomain());
        System.out.println("c2 有效路径 Path  =" + c2.getPath());
        System.out.println("c2 有效寿命 MaxAge=" + c2.getMaxAge()+"秒");
        System.out.println("c2 只http传送 HttpOnly=" + c2.isHttpOnly());
        System.out.println("c2 只https传送 Secure=" + c2.getSecure());
        System.out.println("c2 版本 Version=" + c2.getVersion());
        System.out.println("c2 注释 Comment=" + c2.getComment());

        System.out.println("\n" +c3.getName() + " = " + URLDecoder.decode(c3.getValue(),"utf-8"));
        System.out.println("c3 有效域   Domain=" + c3.getDomain());
        System.out.println("c3 有效路径 Path  =" + c3.getPath());
        System.out.println("c3 有效寿命 MaxAge=" + c3.getMaxAge()+"秒");
        System.out.println("c3 只http传送 HttpOnly=" + c3.isHttpOnly());
        System.out.println("c3 只https传送 Secure=" + c3.getSecure());
        System.out.println("c3 版本 Version=" + c3.getVersion());
        System.out.println("c3 注释 Comment=" + c3.getComment());

        //添加到响应对象中,并响应给客户端
        resp.setContentType("text/html;charset=utf-8");
        resp.addCookie(c1);
        resp.addCookie(c2);
        resp.addCookie(c3);
        resp.getWriter().write("写入客户端名为 username1,username2,username3 小甜饼Cookie,");
    }
}

运行结果如下:

图5 Cookie构造及写入客户端(响应头)

由图5可见,c = new Cookie(...), c.setXXX设置的属性,可用c.getXXX读取。

cn.zzh.cookie.CookieDemo2_zzh.java

package cn.zzh.cookie;

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;

@WebServlet("/cookie/cookieDemo2")
public class CookieDemo2_zzh extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取所有的cookie
        resp.setContentType("text/html;charset=utf-8");
        Cookie[] cookies = req.getCookies();
        for (Cookie cookie : cookies) {
            //获取每一个cookie的名称 name
            String name = cookie.getName();
            if ("username1".equals(name)) {
                //获取username1所对应的value
                String value1 = cookie.getValue();
                resp.getWriter().write("<br>username1=" + value1);
            }
            if ("username2".equals(name)) {
                //获取username2所对应的value
                String value2 = cookie.getValue();
                resp.getWriter().write("<br>username2=" + URLDecoder.decode(value2,"utf-8"));
            }
            if ("username3".equals(name)) {
                //获取username3所对应的value
                String value3 = cookie.getValue();
                resp.getWriter().write("<br>username3=" + URLDecoder.decode(value3,"utf-8"));
            }
        }
    }
}

运行结果如下:

图6 获取指定名称的Cookie(请求头cookie)

cn.zzh.cookie.CookieDemo3_zzh.java

package cn.zzh.cookie;

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.io.PrintStream;
import java.io.PrintWriter;
import java.net.URLDecoder;

@WebServlet("/cookieDemo3")
public class CookieDemo3_zzh extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取所有的cookie
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = resp.getWriter();
        out.write("获取客户端本次请求(当前路径为 /)携带所有的cookie<br>");
        out.write("--有效域Domain、有效路径Path、注释Comment均为null;有效寿命MaxAge=-1<br>");
        out.write("--只http传送HttpOnly、只https传送Secure均为false;版本Version=0<hr>");

        Cookie[] cookies = req.getCookies();
        for (Cookie cookie : cookies) {
            //获取每一个cookie的名称 name
            String name = cookie.getName();
            String value = cookie.getValue();
            out.write(name + " = " + URLDecoder.decode(value,"utf-8")+"<br>");

            System.out.println("\n" +cookie.getName() + "=" +  URLDecoder.decode(value,"utf-8"));
            System.out.println("有效域   Domain=" + cookie.getDomain());
            System.out.println("有效路径 Path  =" + cookie.getPath());
            System.out.println("有效寿命 MaxAge=" + cookie.getMaxAge()+"秒");
            System.out.println("只http传送 HttpOnly=" + cookie.isHttpOnly());
            System.out.println("只https传送 Secure=" + cookie.getSecure());
            System.out.println("版本 Version=" + cookie.getVersion());
            System.out.println("注释 Comment=" + cookie.getComment());        }
    }
}

运行结果如下:

图7 遍历所有有效的Cookie(请求头cookie)--有效路径/

由图7可见,cookies = req.getCookies(); 用cookie.getXXX读取不到有效的属性值,不是null就是false。有效域Domain、有效路径Path、注释Comment均为null;有效寿命MaxAge=-1;只http传送HttpOnly、只https传送Secure均为false;版本Version=0。

cn.zzh.cookie.CookieDemo4_zzh.java

package cn.zzh.cookie;

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.io.PrintWriter;
import java.net.URLDecoder;

@WebServlet("/cookie/cookieDemo4")
public class CookieDemo4_zzh extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取所有的cookie
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = resp.getWriter();
        out.write("获取客户端本次请求(当前路径为:/cookie)携带所有的cookie<br>");

        Cookie[] cookies = req.getCookies();
        for (Cookie cookie : cookies) {
            //获取每一个cookie的名称 name
            String name = cookie.getName();
            String value = cookie.getValue();
            out.write(name + " = " + URLDecoder.decode(value,"utf-8")+"<br>");
        }
    }
}

运行结果如下:

图8 遍历所有有效的Cookie(请求头cookie)--有效路径/,/cookie

cn.zzh.cookie.CookieDemo5_zzh.java

package cn.zzh.cookie;

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.io.PrintWriter;
import java.net.URLDecoder;

@WebServlet("/xxx/aaa/bbb/cookieDemo5")
public class CookieDemo5_zzh extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取所有的cookie
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = resp.getWriter();
        out.write("获取客户端本次请求(当前路径为:/cookie/aaa/bbb/)携带所有的cookie<br>");

        Cookie[] cookies = req.getCookies();
        for (Cookie cookie : cookies) {
            //获取每一个cookie的名称 name
            String name = cookie.getName();
            String value = cookie.getValue();
            out.write(name + " = " + URLDecoder.decode(value,"utf-8")+"<br>");
        }
    }
}

运行结果如下:

图9 遍历所有有效的Cookie(请求头cookie)--有效路径/,/xxx/aaa/bbb
package cn.zzh.cookie;

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.io.PrintWriter;
import java.net.URLDecoder;

@WebServlet("/html/cookieDemo6")
public class CookieDemo6_zzh extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取所有的cookie
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = resp.getWriter();
        out.write("获取客户端本次请求(当前路径为:/,/html)携带所有的cookie<br>");

        Cookie[] cookies = req.getCookies();
        for (Cookie cookie : cookies) {
            //获取每一个cookie的名称 name
            String name = cookie.getName();
            String value = cookie.getValue();
            out.write(URLDecoder.decode(name, "utf-8") + " = " + URLDecoder.decode(value, "utf-8") + "<br>");
        }
    }
}
图10 遍历所有有效的Cookie(请求头cookie)--有效路径/,/html

(4) 任务:显示用户上次访问时间

cn.zzh.cookie.LastTime_zzh.java

package cn.zzh.cookie;

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.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

@WebServlet("/cookie/lastTime")
public class LastTime_zzh extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*
         * 首次登录:
         *   提示  欢迎您首次访问!
         *   记录当前的时间保存在cookie王
         * 下一次(n次)
         * 遍历cookie找到上次存储的cookie名所对应的value
         *   提示  欢迎您回来,您上次访问的时间是:
         *         2021-06-28 14:28
         *     记录当前的时间保存在cookie里
         **/
        resp.setContentType("text/html; charset=utf-8");
        //遍历cookie
        Cookie[] cookies = req.getCookies();
        PrintWriter writer = resp.getWriter();
        boolean flag = true;
        for (Cookie cookie : cookies) {
            String name = cookie.getName();
            //如果有自定义的字段名"lastTime" 不是首次
            if ("lastTime".equals(name)) {
                writer.write("欢迎您回来,您上次访问的时间是: <br>");
                String value = cookie.getValue();//得到的是一个毫秒数的字符串
                //转成毫秒数
                long l = Long.parseLong(value);
                //获取上次登录的日期对象
                Date date = new Date(l);
                //把日期对象转成方便阅读的字符串格式
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 E HH:mm:ss",Locale.CHINA);
                //通过日期转换对象转成想要的日期格式
                String format = sdf.format(date);
                //向浏览器输出
                writer.write(format);
                flag = false;
            }
        }
        if (flag) {
            //没有则首次访问
            writer.write("欢迎您首次访问!");
        }
        //保存当前登录的时间
        Cookie cookie = new Cookie("lastTime", System.currentTimeMillis() + "");

        //设置cookie有效时间7天
        cookie.setMaxAge(60 * 60 * 24 * 7);
        resp.addCookie(cookie);
    }
}

运行结果如下:

图11 最后一次访问时间

3. Session对象

3.1 什么是Session

Session代表着服务器和客户端一次会话的过程。直到Session 失效(服务端关闭),或者客户端关闭时结束。

Session是存储在服务端、并针对每个客户端(客户)通过SessionID来区别不同用户的。Session是以Cookie技术或URL重写实现。默认以Cookie技术实现,服务端会给这次会话创造一个JSESSIONID的Cookie值。

图12 Session存储示意

服务器中与每个客户端浏览器会话(多次网络连接交互、之间间隔不超过最大无活动等待时间,若超过服务器就会结束本次会话)都有1个ID和各自的多个属性数据(属性名-值对)。

3.2 HttpSession API

javax.servlet.http.HttpSession

package javax.servlet.http;

import java.util.Enumeration;
import javax.servlet.ServletContext;

public interface HttpSession {

    public String getId();

    public long getCreationTime();
    public long getLastAccessedTime();
    public int getMaxInactiveInterval();

    public ServletContext getServletContext();
    public Enumeration<String> getAttributeNames();
    public Object getAttribute(String name);

    public void setMaxInactiveInterval(int interval);
    public void setAttribute(String name, Object value);
    public void removeAttribute(String name);

    @Deprecated
    public HttpSessionContext getSessionContext();

    @Deprecated
    public String[] getValueNames();

    @Deprecated
    public Object getValue(String name);

    @Deprecated
    public void putValue(String name, Object value);

    @Deprecated
    public void removeValue(String name);

    public void invalidate();
    public boolean isNew();
}

获取Session对象的方法 ---- req.getSession()

public HttpSession getSession(boolean create)//第一个
  //如果参数create为true,则在相关的HttpSession对象不存在时创建并返回新的HttpSession对象,
  //否则不创建新的HttpSession对象,而是返回null。
public HttpSession getSession()//第二个
方法声明 功能描述
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对象中删除指定名称的属性

cn.zzh.cookie.CookieDemo1_zzh.java

package cn.zzh.session;

import javax.servlet.ServletContext;
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.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

@WebServlet("/sessionDemo1")
public class SessionDemo1_zzh extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //1. 获取session
        HttpSession session = req.getSession();
        //2.调用session对象api方法
        String id = session. getId();
        int maxInactiveInterval = session.getMaxInactiveInterval ();
        long creationTime = session.getCreationTime();
        long lastAccessedTime = session.getLastAccessedTime();
        ServletContext servletContext = session.getServletContext ();
        boolean aNew = session.isNew() ;

        //session立即失效
        //session.invalidate();

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 E HH:mm:ss", Locale.CHINA);

        resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        writer.write( "<hr>会话ID id="+id+"<br>");
        writer.write( "最大交互间隔 maxInactiveInterval="+maxInactiveInterval+"秒<br>");
        writer.write( "创建时间creationTime="+creationTime+"(毫秒)<br>");
        writer.write( "最后一次访问时间lastAccessedTime="+lastAccessedTime+"(毫秒)<br>");
        writer.write( "上下文 servletContext=" +servletContext+"<br>");
        writer.write( "是否新建 aNew="+aNew+"<br><br>");

        writer.write( "<hr>会话ID id="+id+"<br>");
        writer.write( "最大交互间隔 maxInactiveInterval="+maxInactiveInterval/60+"分钟<br>");
        writer.write( "创建时间 creationTime="+sdf.format(new Date(creationTime))+"<br>");
        writer.write( "最后一次访问时间 lastAccessedTime="+sdf.format(new Date(lastAccessedTime))+"<br>");
        writer.write( "上下文路径 servletContext.getContextPath()=" +servletContext.getContextPath()+"<br>");
        writer.write( "是否新建 aNew="+aNew+"<br>");
    }

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

运行结果如下:

图13 认识session对象

3.3 Session的生命周期

Session生效

Sessinon在用户第一次访问服务器时创建,需要注意只有访问JSP(JSP将在第6章讲解)、Servlet等程序时才会创建Session。此外,还可调用request.getSession(true)强制生成Session。只访问HTML、IMAGE等静态资源并不会创建Session。

Session失效1—“超时限制”判断Session是否生效

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

\conf\web.xml Tomcat设置服务器的默认超时间隔

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

② 项目的web.xml文件中配置本项目的Session的失效超时间隔

<session-config>
    <session-timeout>20</session-timeout>
</session-config>

③Servlet程序中手动设置Session的失效时间

session.setMaxInactiveInterval(10*60);//设置单位为秒,设置为-1永不过期

Session失效2—强制Session失效

Servlet程序中强制Session失效

HttpSession session = request.getSession();
session.invalidate();//注销该request的所有session

Session失效3—服务器非正常关闭也会销毁session

cn.zzh.session.SessionDemo2_zzh.java
package cn.zzh.session;

import javax.servlet.ServletContext;
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.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

@WebServlet("/session/sessionDemo2")
public class SessionDemo2_zzh extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //1. 获取session
        HttpSession session = req.getSession();

        //2.调用session对象api方法
        String id = session. getId();
        int maxInactiveInterval = session.getMaxInactiveInterval ();
        long creationTime = session.getCreationTime();
        long lastAccessedTime = session.getLastAccessedTime();
        ServletContext servletContext = session.getServletContext ();
        boolean aNew = session.isNew() ;

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 E HH:mm:ss", Locale.CHINA);

        resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        writer.write( "<hr>会话ID id="+id+"<br>");
        writer.write( "最大交互间隔 maxInactiveInterval="+maxInactiveInterval+"秒<br>");
        writer.write( "创建时间creationTime="+creationTime+"(毫秒)<br>");
        writer.write( "最后一次访问时间lastAccessedTime="+lastAccessedTime+"(毫秒)<br>");
        writer.write( "上下文 servletContext=" +servletContext+"<br>");
        writer.write( "是否新建 aNew="+aNew+"<br><br>");

        writer.write( "<hr>会话ID id="+id+"<br>");
        writer.write( "最大交互间隔 maxInactiveInterval="+maxInactiveInterval/60+"分钟<br>");
        writer.write( "创建时间 creationTime="+sdf.format(new Date(creationTime))+"<br>");
        writer.write( "最后一次访问时间 lastAccessedTime="+sdf.format(new Date(lastAccessedTime))+"<br>");
        writer.write( "上下文路径 servletContext.getContextPath()=" +servletContext.getContextPath()+"<br>");
        writer.write( "是否新建 aNew="+aNew+"<br>");

        //session立即失效
        session.invalidate();
        writer.write( "<hr>从此以后上述Id会话失效,再次执行本程序将是新的Id的会话Session。");
    }
}
图14 session立即失效
package cn.zzh.session;

import javax.servlet.ServletContext;
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.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

@WebServlet("/session/sessionDemo3")
public class SessionDemo3_zzh extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //1. 获取session
        HttpSession session = req.getSession();
        //session最大无活动等待时间=10
        session.setMaxInactiveInterval(10);
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        writer.write( "<hr>执行 session.setMaxInactiveInterval(10); 10秒内Session Id不变;<br>10秒后下述Id会话失效,再次执行本程序将是新的Id、最大无活动等待时间=30秒的会话Session。");

        //2.调用session对象api方法
        String id = session. getId();
        int maxInactiveInterval = session.getMaxInactiveInterval ();
        long creationTime = session.getCreationTime();
        long lastAccessedTime = session.getLastAccessedTime();
        ServletContext servletContext = session.getServletContext ();
        boolean aNew = session.isNew() ;

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 E HH:mm:ss", Locale.CHINA);

        writer.write( "<hr>会话ID id="+id+"<br>");
        writer.write( "最大交互间隔 maxInactiveInterval="+maxInactiveInterval+"秒<br>");
        writer.write( "创建时间creationTime="+creationTime+"(毫秒)<br>");
        writer.write( "最后一次访问时间lastAccessedTime="+lastAccessedTime+"(毫秒)<br>");
        writer.write( "上下文 servletContext=" +servletContext+"<br>");
        writer.write( "是否新建 aNew="+aNew+"<br><br>");

        writer.write( "<hr>会话ID id="+id+"<br>");
        writer.write( "最大交互间隔 maxInactiveInterval="+maxInactiveInterval/60+"分钟<br>");
        writer.write( "创建时间 creationTime="+sdf.format(new Date(creationTime))+"<br>");
        writer.write( "最后一次访问时间 lastAccessedTime="+sdf.format(new Date(lastAccessedTime))+"<br>");
        writer.write( "上下文路径 servletContext.getContextPath()=" +servletContext.getContextPath()+"<br>");
        writer.write( "是否新建 aNew="+aNew+"<br>");
    }
}
图15 设置最大无活动间隔=10秒的session对象

3.4 任务:实现购物车

cn.zzh.cake.entity.Cake.java

package cn.zzh.cake.entity;
public class Cake {
    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;
    }
}

cn.zzh.cake.ntity.CakeDB.java e

package cn.zzh.cake.entity;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
public class CakeDB {
    private static final 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类蛋糕"));
        cake.put("5", new Cake("5", "E类蛋糕"));
    }
    // 获得所有的蛋糕
    public static Collection<Cake> getAll() {
        return cake.values();
    }
    // 根据指定的id获蛋糕
    public static Cake getCake(String id) {
        return cake.get(id);
    }
}

cn.zzh.cake.servle.ListCakeServlet.java

package cn.zzh.cake.servlet;
import cn.zzh.cake.entity.Cake;
import cn.zzh.cake.entity.CakeDB;
import java.io.*;
import java.util.Collection;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;

@WebServlet("/listCakeServlet")
public class ListCakeServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        PrintWriter out = resp.getWriter();
        Collection<Cake> cakes = CakeDB.getAll();
        out.write("本站提供的蛋糕有:<br>");
        for (Cake cake : cakes) {
            String url = "PurchaseServlet?id=" + cake.getId();
            out.write(cake.getName() + "  <a href='" + url
                    + "'>点击购买</a><br>");
        }
    }
}

cn.zzh.cake.servlet.CartServlet.java

package cn.zzh.cake.servlet;
import cn.zzh.cake.entity.Cake;
import java.io.*;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;

@WebServlet("/CartServlet")
public class CartServlet extends HttpServlet {
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = resp.getWriter();
        // 变量cart引用用户的购物车
        List<Cake> cart = null;
        // 变量pruFlag标记用户是否买过商品
        boolean purFlag = true;
        // 获得用户的session
        HttpSession session = req.getSession(false);
        // 如果session为null,purFlag置为false
        if (session == null) {
            purFlag = false;
        } else {
            // 获得用户购物车
            cart = (List) session.getAttribute("cart");
            // 如果用的购物车为null,purFlag置为false
            if (cart == null) {
                purFlag = false;
            }
        }
        /*
         * 如果purFlag为false,表明用户没有购买蛋糕  重定向到ListServlet页面
         */
        if (!purFlag) {
            out.write("对不起!您还没有购买任何商品!<br>");
        } else {
            // 否则显示用户购买蛋糕的信息
            out.write("您购买的蛋糕有:<br>");
            double price = 0;
            for (Cake cake : cart) {
                out.write(cake.getName() + "<br>");
            }
        }
    }
}

cn.zzh.cake.servlet.PurchaseServlet.java

package cn.zzh.cake.servlet;
import cn.zzh.cake.entity.Cake;
import cn.zzh.cake.entity.CakeDB;
import java.io.IOException;
import java.util.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;

@WebServlet("/PurchaseServlet")
public class PurchaseServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        // 获得用户购买的商品
        String id = req.getParameter("id");
        if (id == null) {
            // 如果id为null,重定向到ListBookServlet页面
            String url = "ListBookServlet";
            resp.sendRedirect(url);
            return;
        }
        Cake book = 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);
        }
        // 将商品放入购物车
        cart.add(book);
        // 创建Cookie存放Session的标识号
        Cookie cookie = new Cookie("JSESSIONID", session.getId());
        cookie.setMaxAge(60 * 30);
        cookie.setPath("/Servlet");
        resp.addCookie(cookie);
        // 重定向到购物车页面
        String url = "CartServlet";
        resp.sendRedirect(url);
    }
}

运行结果如下:

图16 购物车

3.5 任务:应用Session对象模拟用户登录

图17 应用Session对象模拟用户登录流程

cn.zzh.login.entityt.User.java session

package cn.zzh.login.entity;
public class User {
    private String 用户名;
    private String 密码;

    public String get用户名() {
        return 用户名;
    }
    public void set用户名(String 用户名) {
        this.用户名 = 用户名;
    }
    public String get密码() {
        return 密码;
    }
    public void set密码(String 密码) {
        this.密码 = 密码;
    }
}

cn.zzh.login.servlet.IndexServlet.java

package cn.zzh.login.servlet;
import cn.zzh.login.entity.User;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;

@WebServlet("/indexServlet")
public class IndexServlet extends HttpServlet {
    public void doGet(HttpServletRequest request,HttpServletResponse response)
            throws ServletException, IOException {
        // 解决乱码问题
        response.setContentType("text/html;charset=utf-8");
        // 创建或者获取保存用户信息的Session对象
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute("user");
        if (user == null) {
            response.getWriter().print(
                    "您还没有登录,请<a href='login.html'>登录</a>");
        } else {
            response.getWriter().print("您已登录,欢迎你," + user.get用户名() + "!");
            response.getWriter().print(
                    "<a href='logoutServlet'>退出</a>");
            // 创建Cookie存放Session的标识号
            Cookie cookie = new Cookie("JSESSIONID", session.getId());
            cookie.setMaxAge(60 * 30);
            cookie.setPath("/ch05_zzh");
            response.addCookie(cookie);
        }
    }
    public void doPost(HttpServletRequest request,
                       HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

cn.zzh.login.servlet.LoginServlet.java

package cn.zzh.login.servlet;
import cn.zzh.login.entity.User;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;

@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
    public void doGet(HttpServletRequest req,
                      HttpServletResponse resp)
            throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        String username = req.getParameter("用户名");
        String password = req.getParameter("密码");
        PrintWriter pw = resp.getWriter();
        //假设正确的用户名是 dzjiang 密码是 123456
        if (("dzjiang").equals(username) && ("123456").equals(password)) {
            User user = new User();
            user.set用户名(username);
            user.set密码(password);
            req.getSession().setAttribute("user", user);
            resp.sendRedirect("indexServlet");
        } else {
            pw.write("用户名或密码错误,登录失败。请<a href='login.html'>登录</a>");
        }
    }
    public void doPost(HttpServletRequest request,
                       HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

cn.zzh.login.servlet.LogoutServlet.java

package cn.zzh.login.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;

@WebServlet("/logoutServlet")
public class LogoutServlet extends HttpServlet {
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
            throws ServletException, IOException {
        // 将Session对象中的User对象移除
        request.getSession().removeAttribute("user");
        response.sendRedirect("indexServlet");
    }
    public void doPost(HttpServletRequest request,
                       HttpServletResponse response)throws ServletException, IOException {
        doGet(request, response);
    }
}

login.html

<!doctype html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>用户登录</title>
</head>
<body>
<h2>用户登录</h2>
<hr>
<form name="reg" action="loginServlet" method="post">
    <label>用户名: <input name="用户名" type="text"></label><br>
    <label>密码: <input name="密码" type="password"></label><br>
    <input type="submit" value="提交" id="bt">
</form>
</body>
</html>

运行结果如下:

图18 用户登录
图19 ch05源代码文件结构






// 实例化----构造
new Cookie(名称,);

// 设置重要属性:存放秒数、有效路径、有效域名
cookie.setMaxAge(int second);
cookie.setPath(int path);
cookie.setMaxAge(int time);

// 向浏览器端添加cookie
resp.addCookie();

// 获取 其实就是读取请求头中的 Cookie值
Cookie[] cookies = req.getCookies();
cookie.setMaxAge(int time);
cookie.setPath(String path);
resp.addCookie(cookie名)
响应头:
Set-Cookie: Cookie名=Cookie值; 属性名1=属性值1; 属性名2=属性值2; ...

Session

(1)原理

(2)生命周期

(3)钝化与活化

(4)session对象的获得方式和存取数据方法(属性数据的设置、提取与删除)

request.getSession();
session.setAttribute("名称",);
session.getAttribute("名称");
session.removeAttribute("名称");

存储于服务器的域对象,对应每次会话,范围小于ServletContext,大于ServletRequest

小结


返回