这里主要研究tomcat中session的管理方式以及sessionId的原理,下文将研究sessionid存到redis中以及基于redis以及redis实现session共享。
平时也就是了解session是基于cookie实现的,cookie是保存在客户端,而session是保存在服务端,对其原来也没有深入理解。下面将深入理解。
1.什么是session
对Tomcat而言,Session是一块在服务器开辟的内存空间,其内部的有一个ConcurrentHashMap,我们做setAttribute和removeAttribute的时候都操作的是此map。(补充一句,request对象的setAttribute也操作的是内部的一个Map)
public class StandardSession implements HttpSession, Session, Serializable { private static final long serialVersionUID = 1L; protected static final boolean STRICT_SERVLET_COMPLIANCE; protected static final boolean ACTIVITY_CHECK; protected static final boolean LAST_ACCESS_AT_START; protected static final String[] EMPTY_ARRAY; protected ConcurrentMap<String, Object> attributes = new ConcurrentHashMap(); ... }
2.Session的目的
Http协议是一种无状态协议,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录;
Session的主要目的就是为了弥补Http的无状态特性。简单的说,就是服务器可以利用session存储客户端在同一个会话期间的一些操作记录;
Session中有一个ConcurrentMap,我们向Session中setAttribute和removeAttribute的时候操作的是此map。
3.简单的研究session的创建时机
3.1实现机制
先看两个问题,如下:
1、服务器如何判断客户端发送过来的请求是属于同一个会话?
答:用Session id区分,Session id相同的即认为是同一个会话,在Tomcat中Session id用JSESSIONID表示;
2、服务器、客户端如何获取Session id?Session id在其之间是如何传输的呢?
答:服务器第一次接收到请求时,开辟了一块Session空间(创建了Session对象),同时生成一个Session id,并通过响应头的Set-Cookie:“JSESSIONID=XXXXXXX”命令,向客户端发送要求设置cookie的响应;
客户端收到响应后,在本机客户端设置了一个JSESSIONID=XXXXXXX的cookie信息,该cookie的过期时间为浏览器会话结束;
接下来客户端每次向同一个网站发送请求时,请求头都会带上该cookie信息(包含Session id);
然后,服务器通过读取请求头中的Cookie信息,获取名称为JSESSIONID的值,得到此次请求的Session id;
ps:服务器只会在客户端第一次请求响应的时候,在响应头上添加Set-Cookie:“JSESSIONID=XXXXXXX”信息,接下来在同一个会话的第二第三次响应头里,是不会添加Set-Cookie:“JSESSIONID=XXXXXXX”信息的;
而客户端是会在每次请求头的cookie中带上JSESSIONID信息;
3.2创建时机简单研究
我们知道request.getSession(boolean create) 获取session,并根据参数动态的获取session,如果传的参数是true的话不存在session就创建一个并返回一个session;如果传false,不存在session也不会创建,如下代码:
package com.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TestServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final Logger LOGGER = LoggerFactory.getLogger(TestServlet.class); public TestServlet() { LOGGER.info("call servlet constructor!"); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session = request.getSession(true);// 传true会创建一个并返回,false不会创建 PrintWriter writer = response.getWriter(); if (session == null) { writer.write("null"); } else { writer.write(session.toString()); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
查看request.etSession(boolean create)的源码并分析:
不带参数的getSession()里面调用的是getSession(true)方法。
public HttpSession getSession() { return this.getSession(true); } public HttpSession getSession(boolean create) { if (this.crossContext) { if (this.context == null) { return null; } else if (this.session != null && this.session.isValid()) { return this.session.getSession(); } else { HttpSession other = super.getSession(false); if (create && other == null) { other = super.getSession(true); } if (other != null) { Session localSession = null; try { localSession = this.context.getManager().findSession(other.getId()); if (localSession != null && !localSession.isValid()) { localSession = null; } } catch (IOException arg4) { ; } if (localSession == null && create) { localSession = this.context.getManager().createSession(other.getId()); } if (localSession != null) { localSession.access(); this.session = localSession; return this.session.getSession(); } } return null; } } else { return super.getSession(create); } }
上述结构图:
分析上面源码:
(1)当前的session存在并且有效(根据session的过期时间以及内部的一些属性进行判断)的话返回session
代码:
else if (this.session != null && this.session.isValid()) { return this.session.getSession(); }
查看this.session.getSession()的源码:(返回真正的session的代码)
下面是StandardSession中的代码:
public HttpSession getSession() { if(this.facade == null) { if(SecurityUtil.isPackageProtectionEnabled()) { this.facade = (StandardSessionFacade)AccessController.doPrivileged(new 1(this, this)); } else { this.facade = new StandardSessionFacade(this); } } return this.facade; }
看到代码是初始化了一个facade(门面)并且返回去。facade又是什么?
protected transient StandardSessionFacade facade = null;
是一个实现HttpSession接口的类
package org.apache.catalina.session; import java.util.Enumeration; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionContext; import org.apache.catalina.session.StandardSession; public class StandardSessionFacade implements HttpSession { private HttpSession session = null; public StandardSessionFacade(StandardSession session) { this.session = session; ... }
(2)接下来研究session不存在的时候session创建并且返回的过程:
参考:http://www.cnblogs.com/chenpi/p/5434537.html
https://www.cnblogs.com/nick-huang/p/6660232.html
https://www.cnblogs.com/interdrp/p/4935614.html
https://www.cnblogs.com/lonelydreamer/p/6169469.html