Tomcat中Session的实现还是比较清晰的,先看类图:
其中HttpSession是Servlet Api,从类图看出来,这里使用了facade模式,StandardSessionFacade为一个门面,而 HttpSession的真正的实现类为StandardSession,它还实现了一个在tomcat内部使用的Session接口,这里讲的都是单台Tomcat的Session,tomcat集群是另外一套的代码。
Manager接口的实现是Session的管理者,保存着这台tomcat中所有的Session.Manager在Context启动的时候被创建,被启动.
在Servlet api中HttpServletRequest的getSession()方法最后面定位到的就是ManagerBase的createSession()方法,当然在这个以前,会查看是否已经存在session,有的话就直接返回了, 所以HttpServletRequest的getSession(boolean create)方法可以指定是否在没有找到session的情况下创建session。
public Session createSession(String sessionId) { // Recycle or create a Session instance 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) { sessionId = generateSessionId(); // FIXME WHy we need no duplication check? /* synchronized (sessions) { while (sessions.get(sessionId) != null) { // Guarantee // uniqueness duplicates++; sessionId = generateSessionId(); } } */ // FIXME: Code to be used in case route replacement is needed /* } else { String jvmRoute = getJvmRoute(); if (getJvmRoute() != null) { String requestJvmRoute = null; int index = sessionId.indexOf("."); if (index > 0) { requestJvmRoute = sessionId .substring(index + 1, sessionId.length()); } if (requestJvmRoute != null && !requestJvmRoute.equals(jvmRoute)) { sessionId = sessionId.substring(0, index) + "." + jvmRoute; } } */ } session.setId(sessionId); sessionCounter++; return (session); }
首先创建一个空的Session,然后是设置各种属性,比较重要的是在sessionId没有传入的情况下,生成一个唯一的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(); } while (sessions.containsKey(result)); return (result); }
在单台tomcat的情况下,jvmRoute是null的,这个随机数产生主要是要平衡,每次要求产生都尽可能的不一样,首先是new一个byte的数组,然后先去文件里面读取随机数,如果不行的话,需要产生一个种子来初始化一个随机数生成器然后生成随机数,之后进行MD5的计算,当然还是要判断一下这个id是不是已经存在了.
StandardManager还有很有意思的方法,在stop()方法中会调用unload()方法,这个方法把session的数据保存到文件中,当然也可以从文件中读书,这个文件存在于tomcat work对应项目目录下面的SESSIONS.ser文件中.
StandardSession中还有几个方法是对应Session Listener的,这些Session Listener是在Servlet Api中的,这样子的话,我们在外面实现这些类的时候,可以对应在Session发生变化的时候,tomcat有个回调我们类的机会
这些listener有:
HttpSessionListener 在session创建和销毁时用(StandardSession.setId(),StandardSession.expire())
HttpSessionActivationListener 在session钝化和反钝化时用(StandardSession.doUnload(),StandardSession.doload())
HttpSessionAttributeListener 在session的attributes属性发生变化时