Tomcat中的session管理,首先的明白一点 : 不是你客户端一个请求传过去,服务器就会为创建一个会话,
不是的,如果后台没有对应的session操作,就是你的servlet中没有session操作(getSession(boolean)),那么就不会给你创建会话;
在开发JEE应用中,通常我们都是对会话进行处理,而对于会话的创建,维护,删除,则是服务器替我们完成!
而在Tomcat中,与会话有关的两个重要的类 : StandardSession , StandardManager(这个manager就像一个组件一样,在tomcat启动的时候就会start) ;
而standardsession 则是实现了HttpSession ; session 只是一个简单的对象就像一个POJO,对session 进行的操作则是通过StandardManger 实现,它继承自
ManagerBase ;在ManagerBase中定义了成员变量:session池:
Map sessions = new ConcurrentHashMap();
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. IMPLEMENTATION NOTE: This object is
* not saved and restored across session serializations!
*/
protected transient Map notes = new Hashtable();
/**
* The authenticated Principal associated with this session, if any.
* IMPLEMENTATION NOTE: This object is not 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没有丢失,对于用户而言,体会不到重启,不影响用户体验。
可以把会话信息写入文件,也可以存入数据库中!