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是在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参数。