文章目录

  • 1. 定义
  • 2. 和Cookie比较
  • 3. 图解
  • 4. 源码解析(Java)
  • 5. 总结


1. 定义

Session在计算机中,尤其是网络应用中,被称为“会话控制”。

Session对象可以存储用户在程序中的一些数据,用户在系统中不同的web页面之间进行跳转时,存储在Session中的数据不会丢失。

当用户请求来自web页面时,如果该用户还没有会话,web服务器就会创建一个新的Session对象。当会话过期或者被放弃后,服务器将终止该会话。

2. 和Cookie比较

Session和Cookie都可以保存用户数据,但是Session是保存在服务端Cookie是保存在客户的浏览器中。

电脑桌面端应用APP应用不保存Cookie。

Session的实现需要依赖于Cookie,当服务端创建Session后,会返回一个JSESSIONID存到Cookie中,下次再请求时,请求头中携带的Cookie会将JSESSIONID一并带回到服务端,这样服务端就可以找到对应的Session对象。

3. 图解

存取session导致乱码 session存放_存取session导致乱码

Session是在servlet中遇到request.getSession()时创建的;JSP本质就是一个servlet,将JSP编译后得到的servlet中,就会有request.getSession()代码,所以访问JSP也会创建Session。

4. 源码解析(Java)

随便在一个接口中加入以下代码:

// 在任意地方获取当前请求的request对象
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 利用request获取session
HttpSession session = request.getSession();

springboot使用内置tomcat启动时,request对象是org.apache.catalina.connector.RequestFacade的实例。

首先进入request.getSession()方法:

@Override
public HttpSession getSession() {
    // 对request的非空判断
    if (request == null) {
        throw new IllegalStateException(
                        sm.getString("requestFacade.nullRequest"));
    }
	// 默认传参true
    return getSession(true);
}

进入getSession(true)方法:

/**
 * @param create 当session不存在时,是否新创建session
 */
@Override
public HttpSession getSession(boolean create) {
	// 对request的非空判断
    if (request == null) {
        throw new IllegalStateException(
                        sm.getString("requestFacade.nullRequest"));
    }
	// 包保护特殊处理逻辑
    if (SecurityUtil.isPackageProtectionEnabled()){
        return AccessController.
            doPrivileged(new GetSessionPrivilegedAction(create));
    } else {
        return request.getSession(create);
    }
}

此处request属于org.apache.catalina.connector.Request类,进入request.getSession(create)方法:

@Override
public HttpSession getSession(boolean create) {
    // 真正获取Session的方法
    Session session = doGetSession(create);
    if (session == null) {
        return null;
    }
	// 获取HttpSession
    return session.getSession();
}

进入doGetSession(create)方法:

protected Session doGetSession(boolean create) {

    // tomcat上下文
    Context context = getContext();
    if (context == null) {
        return null;
    }

    // 如果session不为空且session对象是无效的,则置session为null
    if ((session != null) && !session.isValid()) {
        session = null;
    }
    // 如果session不为空,且是有效的session,则直接返回session
    if (session != null) {
        return session;
    }

    // 获取上下文中管理器对象
    Manager manager = context.getManager();
    // 管理器对象为空说明不支持Session,直接返回null
    if (manager == null) {
        return null;
    }
    // 从Cookie中解析到的JSESSIONID如果不为空
    if (requestedSessionId != null) {
        try {
            // 根据SessionId从管理器对象中查找对应的Session对象
            session = manager.findSession(requestedSessionId);
        } catch (IOException e) {
            // 出错打印日志
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("request.session.failed", requestedSessionId, e.getMessage()), e);
            } else {
                log.info(sm.getString("request.session.failed", requestedSessionId, e.getMessage()));
            }
            session = null;
        }
        // 获取出来的session对象不为空且session已失效,则将session置为null
        if ((session != null) && !session.isValid()) {
            session = null;
        }
        // session对象不为空且session有效
        if (session != null) {
            // 更新session中的最后访问时间为当前时间
            session.access();
            // 返回session
            return session;
        }
    }

    // 如果请求中未传递sessionId且create参数为false,则表示当前请求不存在对应session对象时不新创建
    if (!create) {
        return null;
    }
    // 有效的会话跟踪模式是否包含Cookie模式
    boolean trackModesIncludesCookie =
            context.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.COOKIE);
    // 支持Cookie且response已经被提交(response不能再向缓冲区写入任何东西)则直接抛出异常
    if (trackModesIncludesCookie && response.getResponse().isCommitted()) {
        throw new IllegalStateException(sm.getString("coyoteRequest.sessionCreateCommitted"));
    }
	// 获取sessionId
    String sessionId = getRequestedSessionId();
    if (requestedSessionSSL) {
        
    } else if (("/".equals(context.getSessionCookiePath()) 
            && isRequestedSessionIdFromCookie())) { // 如果sessioncookie的path为/,且sessionId来自于cookie

        if (context.getValidateClientProvidedNewSessionId()) {
            boolean found = false;
            // 获取tomcat中当前Host下所有的Container,在每个container中寻找sessionId对应的session,如果存在对应的session,则标记为found
            for (Container container : getHost().findChildren()) {
                Manager m = ((Context) container).getManager();
                if (m != null) {
                    try {
                        if (m.findSession(sessionId) != null) {
                            found = true;
                            break;
                        }
                    } catch (IOException e) {
                        // Ignore. Problems with this manager will be
                        // handled elsewhere.
                    }
                }
            }
            // 如果都没有,则将sessionId置空
            if (!found) {
                sessionId = null;
            }
        }
    } else {
        sessionId = null;
    }
    // 创建session对象
    session = manager.createSession(sessionId);

    // 基于session对象创建一个session cookie
    if (session != null && trackModesIncludesCookie) {
        // 创建Cookie对象,并设置MaxAge、Comment、Domain、Secure、HttpOnly、Path参数
        Cookie cookie = ApplicationSessionCookieConfig.createSessionCookie(
                context, session.getIdInternal(), isSecure());
		// 向response中增加Set-Cookie响应头
        response.addSessionCookieInternal(cookie);
    }

    if (session == null) {
        return null;
    }
	// 设置访问时间为当前时间
    session.access();
    return session;
}

判断session是否失效的session.isValid()方法:

@Override
public boolean isValid() {

    if (!this.isValid) {
        return false;
    }

    if (this.expiring) {
        return true;
    }

    if (ACTIVITY_CHECK && accessCount.get() > 0) {
        return true;
    }
	
    // 最大存活时间是否大于0
    if (maxInactiveInterval > 0) {
        // 获取存活时间(s)
        int timeIdle = (int) (getIdleTimeInternal() / 1000L);
        // 存活时间超过最大存活时间则使其失效
        if (timeIdle >= maxInactiveInterval) {
            expire(true);
        }
    }

    return this.isValid;
}

5. 总结

Session是一项会话存储技术,它的实现需要Cookie的配合。服务端根据请求中Cookie携带的JSESSIONID参数寻找对应的Session对象。

第一次请求没有携带JSESSIONID或者JSESSIONID对应的Session对象已经失效或者不存在,则服务端创建新的Session,并将JSESSIONID添加到响应头中,浏览器端接收到响应后,设置Cookie中的JSESSIONID参数。