会话跟踪技术

会话是指一个终端用户与交互系统进行通讯的过程,例如从浏览器发出请求到服务端响应数据给前端这个过程即为一个会话。

会话跟踪指的是一种维护浏览器状态的方法。服务器会收到多个请求,这多个请求可能来自多个浏览器。服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据。

浏览器和服务器不支持数据共享的原因:

  • HTTP协议是无状态的,每次浏览器向服务器请求时,服务器都会将该请求视为新的请求。
  • HTTP协议设计成无状态的目的是让每次请求之间相互独立,互不影响。
  • 请求与请求之间独立后,就无法实现多次请求之间的数据共享。

实现会话跟踪技术的方式有:

  • 客户端会话跟踪技术:Cookie。 Cookie是存储在浏览器端。
  • 服务端会话跟踪技术:Session。 Session是存储在服务器端。

Cookie

Cookie是一种客户端会话技术,将数据保存到客户端,在以后的每次请求中都携带Cookie数据进行访问。

服务器将Cookie发送给浏览器,是通过在响应头中添加Set-Cookie键值对实现的,如Set-Cookie: message="Hello Wrold"。在多个Cookie之间使用;分割。

浏览器在接收了服务器发送来的Cookie后,会将数据存储在浏览器的内存中。在接下来的每次请求中,浏览器都会将获取到的Cookie发送到服务器(同一次会话中或在Cookie存活时间内)。浏览器将Cookie发送到服务器,是通过在请求头中设置Cookie键值对实现的,如Cookie: message="Hello Wrold"。同样的,在多个Cookie之间使用;分割。

  • 创建Cookie对象:

    Cookie cookie = new Cookie("key","value")
    
  • 发送Cookie到客户端(使用Response对象):

    response.addCookie(cookie)
    
  • 从客户端获取Cookie(使用Request对象):

    request.getCookies()
    

创建Maven web项目。

项目依赖:

<packaging>war</packaging>

<dependencies>
    <!--servlet-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

<build>
    <pluginManagement>
        <plugins>
            <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.2</version>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

发送Cookie使用的是HttpServletResponse对象提供的addCookie()

response.addCookie(cookie)
package web.cookie;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

@WebServlet("/cookieServlet")
public class CookieServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 创建Cookie对象
        String value = "Hello World";
        System.out.println("存储数据: " + value);
        Cookie cookie = new Cookie("message", value);

        // 设置存活时间, 7天
        cookie.setMaxAge(60*60*24*7);

        // 2. 发送Cookie, response
        response.addCookie(cookie);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

在发送Cookie时,可以使用setMaxAge()方法设置Cookie存活的时间(单位:秒)。

如果要在Cookie中存储中文数据,需要将中文数据使用URLEncoder.encode()按照UTF-8(与前端页面的编码保持一致)编码。

package web.cookie;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

@WebServlet("/cookieServlet")
public class CookieServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 创建Cookie对象
        // 存储中文
        String value = "你好";
        // URL编码
        value = URLEncoder.encode(value, StandardCharsets.UTF_8);
        // System.out.println("存储数据: " + value);
        Cookie cookie = new Cookie("message", value);

        // 设置存活时间, 7天
        cookie.setMaxAge(60*60*24*7);

        // 2. 发送Cookie, response
        response.addCookie(cookie);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

从客户端获取Cookie使用的是HttpServletRequest对象提供的getCookies()

request.getCookies()

它返回一个Cookie数组(Cookie[])。

package web.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.nio.charset.StandardCharsets;

@WebServlet("/cookieServlet")
public class CookieServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取Cookie数组
        Cookie[] cookies = request.getCookies();

        // 2. 遍历数组
        for (Cookie cookie : cookies) {
            // 3. 获取数据
            String name = cookie.getName();
            if ("message".equals(name)) {
                String value = cookie.getValue();
                // URL解码
                value = URLDecoder.decode(value, StandardCharsets.UTF_8);

                System.out.println(name + ":" + value);
                break;
            }
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

Cookie对象提供的getName()getValue()可以分别获取Cookie的名称和值。

获取Cookie后应该使用URLDecoder.decode进行解码,这样确保Cookie里出现中文数据时不会乱码。

方法 说明
Cookie[] getCookies() HttpServletRequest提供的获取客户端Cookie的方法
void addCookie(Cookie cookie) HttpServletResponse提供的,将Cookie发送到客户端的方法
Cookie(String name, String value) Cookie类的构造函数,用于创建Cookie,需要指定名称name和值value
void setMaxAge(int expiry) 设置Cookie过期的时间(单位:秒)。值为-1(默认情况下)或其它负数,Cookie只会在当前Session会话中持续有效。值为0表示删除对应Cookie
int getMaxAge() 返回Cookie的最大生存周期(单位:秒)。默认情况下(不设置过期时间),-1 表示 Cookie 将持续到浏览器关闭。
String getName() 返回Cookie的名称(名称在创建后不能改变)
void setValue(String newValue) 设置与Cookie关联的值
String getValue() 获取与Cookie关联的值

Session

Session是一种服务端会话跟踪技术,其数据保存在服务端。Session因为是存储在服务端所以比起Cookie,Session要更加安全。但与之相对的,Session不适合长期保存数据。

Session是基于Cookie实现的。Session要想实现一次会话多次请求之间的数据共享,就必须要保证多次请求获取Session的对象是同一个。在第一次获取Session对象时,Session对象会有一个唯一的标识,如id:123。Tomcat服务器发现业务处理中使用了Session对象,就会把Session的唯一标识id:123当做一个Cookie,添加Set-Cookie:JESSIONID=123到响应头中,并响应给浏览器。所以,浏览器通过Cookie告诉服务器Session的id,服务器也通过Cookie获取相应的Session,从而实现一次会话多次请求之间的数据共享。

JavaEE中提供了HttpSession接口,来实现一次会话的多次请求之间数据共享功能:

  • 获取Session对象(使用Request对象):

    HttpSession session = request.getSession();
    
  • Session对象提供的功能:

    • 存储数据到 session 域中:

      void setAttribute(String name, Object o)
      
    • 根据 key,获取值:

      Object getAttribute(String name)
      
    • 根据 key,删除该键值对:

      void removeAttribute(String name)
      

存储数据

package web.session;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet("/sessionServlet")
public class SessionServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 存储数据到Session中
        // 1. 获取Session对象
        HttpSession session = request.getSession();
        // 2. 存储数据
        session.setAttribute("message", "Hello World!");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

由于Session中的数据是存储在服务器,所以在Session中存储和读取中文数据不需要进行编码和解码。

获取数据

package web.session;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet("/sessionServlet")
public class SessionServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 从Session中获取数据
        // 1. 获取Session对象
        HttpSession session = request.getSession();
        // 2. 获取数据
        Object message = session.getAttribute("message");
        System.out.println(message);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

Session 的钝化与活化

正常来说,Session是存储在内存中的,只要服务器关闭,那么所有会话的Session都会失效。但是Tomcat在正常关闭的情况下,Session并不会失效,这是因为Session的钝化:在服务器正常关闭后,Tomcat会自动将Session数据写入硬盘的文件中。钝化的数据路径为:项目目录\target\tomcat\work\Tomcat\localhost\项目名称\SESSIONS.ser

再次启动服务器后,从文件中加载数据到Session中,这就是Session的活化。而数据加载到Session中后,路径中的SESSIONS.ser文件会被删除掉。

所以说只要浏览器中记录SessionID的Cookie还存在,那么这个对应的Session也就存在,并且唯一。

而Cookie的存在是与浏览器的关闭密切相关的。在无人为销毁Cookie和没有为Cookie设置存活时间的情况下,当浏览器关闭时,其记录的Cookie也会自动销毁。所以当浏览器关闭时,对应的Session可能会消失。

Session 的销毁

默认情况下,无操作,30分钟后自动销毁Session。对于这个失效时间,是可以通过在项目的web.xml中配置进行修改:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <session-config>
        <session-timeout>100</session-timeout>
    </session-config>
</web-app>

如果没有配置,默认是30分钟,默认值是在Tomcat的web.xml配置文件中写死的。通过在Tomcat的web.xml中搜索<session-timeout>即可找到对应设置。

也可以通过调用HTTPSessioninvalidate()方法进行手动销毁。

Session 常用方法

方法 说明
HttpSession getSession() HttpServletRequest提供的方法,用于获取当前会话的HttpSession对象
Object getAttribute(String name) 返回在该 session 会话中具有指定名称(name)的对象;如果没有指定名称的对象,则返回 null
void setAttribute(String name, Object value) 使用指定的名称绑定一个对象到该 session 会话
void removeAttribute(String name) 将从该 session 会话移除指定名称的对象
Enumeration getAttributeNames() 返回 String 对象的枚举,String 对象包含所有绑定到该 session 会话的对象的名称
String getId() 返回一个包含分配给该 session 会话的唯一标识符的字符串
void invalidate() 销毁当前Session对象。该方法指示该 session 会话无效,并解除绑定到它上面的任何对象

Cookie 和 Session

Cookie 和 Session 的区别:

  • 存储位置:Cookie 是将数据存储在客户端,Session 将数据存储在服务端。
  • 安全性:Cookie不安全,Session安全。
  • 数据大小:Cookie最大3KB,Session无大小限制。
  • 存储时间:Cookie可以通过setMaxAge()长期存储,Session默认30分钟。
  • 服务器性能:Cookie不占服务器资源,Session占用服务器资源。

Session是基于Cookie实现的。