Tomcat中的session管理,首先的明白一点 : 不是你客户端一个请求传过去,服务器就会为创建一个会话,
不是的,如果后台没有对应的session操作,就是你的servlet中没有session操作(getSession(boolean)),那么就不会给你创建会话;
在开发JEE应用中,通常我们都是对会话进行处理,而对于会话的创建,维护,删除,则是服务器替我们完成!
而在Tomcat中,与会话有关的两个重要的类 : StandardSession , StandardManager(这个manager就像一个组件一样,在tomcat启动的时候就会start) ;
而standardsession 则是实现了HttpSession ; session 只是一个简单的对象就像一个POJO,对session 进行的操作则是通过StandardManger 实现,它继承自
ManagerBase ;在ManagerBase中定义了成员变量:session池:
Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();
StandardSession中的属性:
/** * The collection of user data attributes associated with this Session. */ protected Map attributes = new ConcurrentHashMap(); /** * The time this session was created, in milliseconds since midnight, * January 1, 1970 GMT. */ protected long creationTime = 0L; /** * The last accessed time for this Session. */ protected volatile long lastAccessedTime = creationTime; /** * The session event listeners for this Session. */ protected transient ArrayList listeners = new ArrayList(); /** * The Manager with which this Session is associated. */ protected transient Manager manager = null; * The maximum time interval, in seconds, between client requests before * the servlet container may invalidate this session. A negative time * indicates that the session should never time out. */ protected int maxInactiveInterval = -1; /** * Flag indicating whether this session is new or not. */ protected boolean isNew = false; /** * Flag indicating whether this session is valid or not. */ protected volatile boolean isValid = false; /** * Internal notes associated with this session by Catalina components * and event listeners. <b>IMPLEMENTATION NOTE:</b> This object is * <em>not</em> saved and restored across session serializations! */ protected transient Map notes = new Hashtable(); /** * The authenticated Principal associated with this session, if any. * <b>IMPLEMENTATION NOTE:</b> This object is <i>not</i> saved and * restored across session serializations! */ protected transient Principal principal = null; /** * The access count for this session. */ protected transient AtomicInteger accessCount = null;
session的创建过程 :
调用getSession(boolean create) // 取得会话对象,如果没有session, create = true时 ,则会创建一个,create=false , 返回null
public HttpSession getSession(boolean create) { Session session = doGetSession(create); if (session != null) { return session.getSession(); } else { return null; } }
doGetSession(create) :
protected Session doGetSession(boolean create) { …… // 先获取所在context的manager对象 Manager manager = null; if (context != null) manager = context.getManager(); if (manager == null) return (null); // Sessions are not supported //这个requestedSessionId就是从Http request中解析出来的 if (requestedSessionId != null) { // 如果之前存在session try { //manager管理的session池中找相应的session对象 session = manager.findSession(requestedSessionId); } catch (IOException e) { session = null; } //判断session是否为空及是否过期超时 if ((session != null) && !session.isValid()) session = null; if (session != null) { //session对象有效,记录此次访问时间 session.access(); return (session); } } // 如果参数是false,则不创建新session对象了,直接退出了 if (!create) return (null); if ((context != null) && (response != null) && context.getCookies() && response.getResponse().isCommitted()) { throw new IllegalStateException (sm.getString("coyoteRequest.sessionCreateCommitted")); } // 开始创建新session对象 if (connector.getEmptySessionPath() && isRequestedSessionIdFromCookie()) { session = manager.createSession(getRequestedSessionId()); // 调用manager中方法创建session } else { session = manager.createSession(null); } // 将新session的jsessionid写入cookie,传给browser if ((session != null) && (getContext() != null) && getContext().getCookies()) { Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME, session.getIdInternal()); configureSessionCookie(cookie); response.addCookieInternal(cookie); // response中的cookies就是在这里写入的 } //记录session最新访问时间 if (session != null) { session.access(); return (session); } else { return (null); } }
接下来进入StandardManager的createSession方法:
public Session createSession(String sessionId) { //是个session数量控制逻辑,超过上限则抛异常退出 if ((maxActiveSessions >= 0) && (sessions.size() >= maxActiveSessions)) { rejectedSessions++; throw new IllegalStateException (sm.getString("standardManager.createSession.ise")); } return (super.createSession(sessionId)); // 调用ManagerBase中的 }
public Session createSession(String sessionId) { // 创建一个新的StandardSession对象 Session session = createEmptySession(); // Initialize the properties of the new session and return it session.setNew(true); session.setValid(true); session.setCreationTime(System.currentTimeMillis()); session.setMaxInactiveInterval(this.maxInactiveInterval); if (sessionId == null) { //设置jsessionid sessionId = generateSessionId(); } // 就是这里顺便把session加入session池中 session.setId(sessionId); sessionCounter++; return (session); }
setId() 方法 :把session对象加入session池中!
中间省略set(String id) ;
public void setId(String id, boolean notify) { if ((this.id != null) && (manager != null)) manager.remove(this); this.id = id; if (manager != null) manager.add(this); // 添加session到session池好隐蔽啊!!! if (notify) { tellNew(); } }
generateSessionId() 生成sessionId:
protected synchronized String generateSessionId() { byte random[] = new byte[16]; String jvmRoute = getJvmRoute(); String result = null; // Render the result as a String of hexadecimal digits StringBuffer buffer = new StringBuffer(); do { int resultLenBytes = 0; if (result != null) { buffer = new StringBuffer(); duplicates++; } while (resultLenBytes < this.sessionIdLength) { getRandomBytes(random); random = getDigest().digest(random); for (int j = 0; j < random.length && resultLenBytes < this.sessionIdLength; j++) { byte b1 = (byte) ((random[j] & 0xf0) >> 4); byte b2 = (byte) (random[j] & 0x0f); if (b1 < 10) buffer.append((char) ('0' + b1)); else buffer.append((char) ('A' + (b1 - 10))); if (b2 < 10) buffer.append((char) ('0' + b2)); else buffer.append((char) ('A' + (b2 - 10))); resultLenBytes++; } } if (jvmRoute != null) { buffer.append('.').append(jvmRoute); } result = buffer.toString(); //注意这个do…while结构 } while (sessions.containsKey(result)); // sessions就是那个session池,concurrentHashMap , 检查池中是否包含该key return (result); }
到此 一个session对象就创建了!
Session注销:
主动注销
调用sesion的servlet接口方法:
session.invalidate();
// StanardSession中的invalidate()
public void invalidate() { if (!isValidInternal()) throw new IllegalStateException (sm.getString("standardSession.invalidate.ise")); // 明显的注销方法 expire(); // expire 终止 }
超时注销
我们可以在web.xml中配置session的超时时间,也就是过了这个时间点,session就失效了,
如果要你来设计,你会怎么做,每一个会话接入时,就会去扫描map一次,找到那些超时的,然后剔除吗!
这样,效率是不是降低了!
那么,就是当你用这个会话的时候,再把它取出来,然后比较它是否过期,如果过期了,那么就重新创建一个会话!
tomcat就是这么处理超时的!
看源码 :
public boolean isValid() { …… //这就是判断距离上次访问是否超时的过程 if (maxInactiveInterval >= 0) { long timeNow = System.currentTimeMillis(); int timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L); if (timeIdle >= maxInactiveInterval) { expire(true); } } return (this.isValid); }
expire() 方法:
public void expire(boolean notify) { synchronized (this) { ...... //设立标志位 setValid(false); //计算一些统计值,例如此manager下所有session平均存活时间等 long timeNow = System.currentTimeMillis(); int timeAlive = (int) ((timeNow - creationTime)/1000); synchronized (manager) { if (timeAlive > manager.getSessionMaxAliveTime()) { manager.setSessionMaxAliveTime(timeAlive); } int numExpired = manager.getExpiredSessions(); numExpired++; manager.setExpiredSessions(numExpired); int average = manager.getSessionAverageAliveTime(); average = ((average * (numExpired-1)) + timeAlive)/numExpired; manager.setSessionAverageAliveTime(average); } // 将此session从manager对象的session池中删除 manager.remove(this); ...... } }
Session的持久化 和 启动初始化:
tomcat执行安全退出时(通过执行shutdown脚本),会将session持久化到本地文件,通常在tomcat的部署目录下有个session.ser文件。当启动tomcat时,会从这个文件读入session,并添加到manager的session池中 去。 这样,当tomcat正常重启时, session没有丢失,对于用户而言,体会不到重启,不影响用户体验。
可以把会话信息写入文件,也可以存入数据库中!