上面代码就是根据 requestedSessionId 去查找session,找不到就创建新的session。那么 requestedSessionId 怎么获取的呢?protected Session doGetSession(boolean create) {
// There cannot be a session if no context has been assigned yet
if (context == null)
return (null);
// Return the current session if it exists and is valid
if ((session != null) && !session.isValid())
session = null;
if (session != null)
return (session);
// Return the requested session if it exists and is valid
Manager manager = null;
if (context != null)
manager = context.getManager();
if (manager == null)
return (null); // Sessions are not supported
if (requestedSessionId != null) {
try {
session = manager.findSession(requestedSessionId);
} catch (IOException e) {
session = null;
}
if ((session != null) && !session.isValid())
session = null;
if (session != null) {
session.access();
return (session);
}
}
// Create a new session if requested and the response is not committed
if (!create)
return (null);
if ((context != null) && (response != null) &&
context.getCookies() &&
response.getResponse().isCommitted()) {
throw new IllegalStateException
(sm.getString("coyoteRequest.sessionCreateCommitted"));
}
// Attempt to reuse session id if one was submitted in a cookie
// Do not reuse the session id if it is from a URL, to prevent possible
// phishing attacks
if (connector.getEmptySessionPath()
&& isRequestedSessionIdFromCookie()) {
session = manager.createSession(getRequestedSessionId());
} else {
session = manager.createSession(null);
}
// Creating a new session cookie based on that session
if ((session != null) && (getContext() != null)
&& getContext().getCookies()) {
String scName = context.getSessionCookieName();
if (scName == null) {
scName = Globals.SESSION_COOKIE_NAME;
}
Cookie cookie = new Cookie(scName, session.getIdInternal());
configureSessionCookie(cookie);
response.addSessionCookieInternal(cookie, context.getUseHttpOnly());
}
if (session != null) {
session.access();
return (session);
} else {
return (null);
}
}
类RequestFacade的getSession方法:
(1) Manager:定义了关联到某一个容器的用来管理session池的基本接口。这是catalina包下的。
(2) ManagerBase:实现了Manager接口,该类提供了Session管理器的常见功能的实现。
(3) StandardManager:继承自ManagerBase,tomcat的默认Session管理器(不指定配置,默认使用这个),是tomcat处理session的非集群实现(也就说是单机版的),tomcat关闭时,内存session信息会持久化到磁盘保存为SESSION.ser,再次启动时恢复。
(4) PersistentManagerBase:继承自ManagerBase,实现了和定义了session管理器持久化的基础功能。
(5) PersistentManager:继承自PersistentManagerBase,主要实现的功能是会把空闲的会话对象(通过设定超时时间)交换到磁盘上。
(6)StoreBase:存放持久化有关store接口的基本实现。
接口Store及其实例是为session管理器提供了一套存储策略,store定义了基本的接口,而StoreBase提供了基本的实现。其中FileStore类实现的策略是将session存储在以setDirectory()指定目录并以.session结尾的文件中的。JDBCStore类是将Session通过JDBC存入数据库中,因此需要使用JDBCStore,需要分别调用setDriverName()方法和setConnectionURL()方法来设置驱动程序名称和连接URL。
(7) StandardSession :tomcat的session实现类。
2.4 Tomcat session相关的配置
从两个层面总结一下session相关的配置和设置。首先是从配置文件层面,session是有过期时间的,这个默认的过期时间是 在$catalina_home/conf/web.xml有定义的。具体的默认配置如下(默认的过期时间是30min,即30min没有访 问,session就过期了):
还有一点就是session管理如果不配置就默认使用StandardManager,但如果要配置的话可以 在$catalina_home/conf/context.xml当中指定(其中从这个配置当中可以看到session管理器是和context容器关 联的,也就说每个web应用都会有一个session管理器)具体的配置如下:
Tomcat7.x默认这个manager的配置是注释掉的。如果要指定的PersistentManager为默认管理器的话可以这么指定:
其实看到这也就发现了,其实session管理器或者Store存储策略,只要实现了相关的接口,都是可以自定义的。自己写一个配置在这里就ok了。
另外在从代码层面总结一下:session的一些配置信息是写死在代码里的,比如SessionConfig这个类就定义了一些session的设 置信息。Session在cookie中的名字是JSESSION. Session通过URL重写的方式放在path里时,键值的名字是jsessionids,具体的代码如下:
还有一点就是sessionId默认指定的长度是16个字节,这个在SessionIdGenerator当中指定:
这个 SessionIdGenerator应该是tomcat7的。我在6上源码找不到。关于tomcat6的源码里面,有这个方法generateSessionId:
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);
}
实现原理:一个随机数加时间加上jvm的id值,jvm的id值会根据服务器的硬件信息计算得来,因此不同jvm的id值都是唯一的
再补充一点:session本身也是基于kv的hashmap实现的,比如:StandardSession 里面
protected Map attributes = new ConcurrentHashMap();
所有的会话信息的存取都是通过这个属性来实现的。另外上面web.xml的配置有效期tomcat是怎么实现的呢?
就是ManagerBase.processExpires,一直查询所有的session对象,每个检查是否有效,超期就删除。
具体代码参见
其中session在expire会触发相应的listener事件,从而对session信息进行监控,下面是expire方法部分截图。
**********************总结*********************
由上面所描述的session实现机制,我们会发现,为了弥补http协议的无状态的特点,服务端会占用一定的内存和cpu用来存储和处理session计算的开销,这也就是tomcat这个的web容器的并发连接那么低(tomcat官方文档里默认的连接数是200)原因之一。因此很多java语言编写的网站,在生产环境里web容器之前会加一个静态资源服务器,例如:apache服务器或nginx服务器,静态资源服务器没有解决http无状态问题的功能,因此部署静态资源的服务器也就不会让出内存或cpu计算资源专门去处理像session这样的功能,这些内存和cpu资源可以更有效的处理每个http请求,因此静态资源服务器的并发连接数更高,所以我们可以让那些没有状态保持要求的请求直接在静态服务器里处理,而要进行状态保持的请求则在java的web容器里进行处理,这样能更好的提升网站的效率
由上所述,session一共有两个问题需要解决:
1) session的存储应该独立于web容器,也要独立于部署web容器的服务器;
2)如何进行高效的session同步。
下面是一篇用zookeeper实现的分布式session方案:
http://www.open-open.com/lib/view/open1378556537303.html