tomcat容器通過一個稱為Session管理器的組件來管理建立的Session對象,該組件由org.apache.catalina.Manager接口表示;Session管理器必須與一個Context容器相關聯(需要用到Context容器的相關上下文或方法)。
默認情況下,Session管理器會將其所管理的 Session對象存放在內存中,不過在tomcat中,Session管理器也庫將Session對象持久化,存儲到文件存儲器或通過JDBC寫入到數據庫中。
下面我們來分析具體實現,在servlet編程方面中,Session對象由javax.servlet.http.HttpSession接口表示;在tomcat中該接口的標準實現是org.apache.catalina.session包下的StandardSession類,該類同時實現了org.apache.catalina.Session接口,在tomcat內部供Session管理器使用;而實際交給servlet實例使用的是Session接口的外觀類StandardSessionFacade
下面是Session管理器內部使用的Session接口
public
interface
Session {
public
static
final
String SESSION_CREATED_EVENT = "createSession"
;
public
static
final
String SESSION_DESTROYED_EVENT = "destroySession"
;
public
String getAuthType();
public
void
setAuthType(String authType);
public
long
getCreationTime();
public
void
setCreationTime(
long
time);
public
String getId();
public
void
setId(String id);
public
String getInfo();
public
long
getLastAccessedTime();
public
Manager getManager();
public
void
setManager(Manager manager);
public
int
getMaxInactiveInterval();
public
void
setMaxInactiveInterval(
int
interval);
public
void
setNew(
boolean
isNew);
public
Principal getPrincipal();
public
void
setPrincipal(Principal principal);
public
HttpSession getSession();
public
void
setValid(
boolean
isValid);
public
boolean
isValid();
public
void
access();
public
void
addSessionListener(SessionListener listener);
public
void
expire();
public
Object getNote(String name);
public
Iterator getNoteNames();
public
void
recycle();
public
void
removeNote(String name);
public
void
removeSessionListener(SessionListener listener);
public
void
setNote(String name, Object value);
}
Session對象總是存在于Session管理器中,可以通過setManager()方法將Session實例 與某個Session管理器相關聯;Session管理器可以通過setId()方法設置Session標識符;同時會調用getLastAccessedTime()方法判斷一個Session對象的有效性;setValid()方法用于重置該Session對象的有效性;每當訪問一個Session實例時,會調用access()方法來修改Session對象的最后訪問時間;最后,Session管理器會調用Session對象的expire()方法使其過期,通過getSession()方法獲取一個經過外觀類StandardSessionFacade包裝的HttpSession對象
StandardSession類是Session接口的標準實現,同時實現了javax.servlet.http.HttpSession接口和java.lang.Serializable接口
(注:StandardSession類實現HttpSession接口方法的實現基本上都依賴于實現Session接口的方法對StandardSession實例的填充,因此我們可以想象,StandardSession類基本上類似與適配器模式中的Adapter角色,實現了原類型(Session接口類型)到目標接口的轉換(HttpSession接口))
其構造函數接受一個Manager接口的實例,迫使Session對象必須擁有一個Session管理器實例
public
StandardSession(Manager manager) {
super
();
this
.manager =
manager;
if
(manager
instanceof
ManagerBase)
this
.debug =
((ManagerBase) manager).getDebug();
}
下面是StandardSession實例的一些比較重要的私有成員變量
//
存儲session的鍵值對
private
HashMap attributes =
new
HashMap();
private
transient
String authType =
null
;
//
創建時間
private
long
creationTime = 0L
;
//
是否過期
private
transient
boolean
expiring =
false
;
//
外觀類
private
transient
StandardSessionFacade facade =
null
;
//
session標識
private
String id =
null
;
//
最后訪問時間
private
long
lastAccessedTime =
creationTime;
//
監聽器聚集
private
transient
ArrayList listeners =
new
ArrayList();
//
session管理器
private
Manager manager =
null
;
//
清理session過期時間
private
int
maxInactiveInterval = -1
;
private
boolean
isNew =
false
;
private
boolean
isValid =
false
;
//
當前訪問時間
private
long
thisAccessedTime = creationTime;
其中getSession()方法通過傳入自身實例來創建外觀類StandardSessionFacade實例
public
HttpSession getSession() {
if
(facade ==
null
)
facade
=
new
StandardSessionFacade(
this
);
return
(facade);
}
如果session管理器中的某個Session對象在某個時間長度內都沒有被訪問的話,會被Session管理器設置為過期,這個時間長度是由變量maxInactiveInterval的值來指定
public
void
expire(
boolean
notify) {
//
Mark this session as "being expired" if needed
if
(expiring)
return
;
expiring
=
true
;
setValid(
false
);
//
Remove this session from our manager's active sessions
if
(manager !=
null
)
manager.remove(
this
);
//
Unbind any objects associated with this session
String keys[] =
keys();
for
(
int
i = 0; i < keys.length; i++
)
removeAttribute(keys[i], notify);
//
Notify interested session event listeners
if
(notify) {
fireSessionEvent(Session.SESSION_DESTROYED_EVENT,
null
);
}
//
Notify interested application event listeners
//
FIXME - Assumes we call listeners in reverse order
Context context =
(Context) manager.getContainer();
Object listeners[]
=
context.getApplicationListeners();
if
(notify && (listeners !=
null
)) {
HttpSessionEvent event
=
new
HttpSessionEvent(getSession());
for
(
int
i = 0; i < listeners.length; i++
) {
int
j = (listeners.length - 1) -
i;
if
(!(listeners[j]
instanceof
HttpSessionListener))
continue
;
HttpSessionListener listener
=
(HttpSessionListener) listeners[j];
try
{
fireContainerEvent(context,
"beforeSessionDestroyed"
,
listener);
listener.sessionDestroyed(event);
fireContainerEvent(context,
"afterSessionDestroyed"
,
listener);
}
catch
(Throwable t) {
try
{
fireContainerEvent(context,
"afterSessionDestroyed"
,
listener);
}
catch
(Exception e) {
;
}
//
FIXME - should we do anything besides log these?
log(sm.getString("standardSession.sessionEvent"
), t);
}
}
}
//
We have completed expire of this session
expiring =
false
;
if
((manager !=
null
) && (manager
instanceof
ManagerBase)) {
recycle();
}
}
上面方法中從Session管理器移除該session實例并觸發一些事件,同時設置一些內部變量的值。
為了傳遞一個session對象給servlet實例,tomcat的session管理器會實例化StandardSession類,并填充該session對象;不過,為了阻止servlet程序員訪問StandardSession實例中的一些敏感方法,tomcat容器將StandardSession實例封裝為StandardSessionFacade類型的實例(實現HttpRequest接口的HttpRequestBase類的doGetSession方法里面調用session管理器的createSession()方法返回StandardSession類的實例,然后調用StandardSession實例 的getSession()方法返回StandardSessionFacade類型實例),該類僅僅實現了javax.servlet.http.HttpSession接口中的方法,這樣servlet程序員就不能將HttpSession對象向下轉型為StandardSession類型
下面接下來描述session管理器,session管理器是org.apache.catalina.Manager接口的實例,抽象類ManagerBase實現了Manager接口,提供了常見功能的實現;ManagerBase類有兩個直接子類,分別為StandardManager類和PersistentManagerBase類
當tomcat運行時,StandardManager實例將session對象存儲在內存中;當tomcat關閉時,它會將當前內存中所有的session對象序列化存儲到文件中;當在此運行tomcat時,又會將這些session對象重新載入內存。
另外,繼承自PersistentManagerBase類的session管理器會將session對象存儲到輔助存儲器中,包括PersistentManager類和DistributedManager類(tomcat4中)
下面是Manager接口的方法聲明:
public
interface
Manager {
public
Container getContainer();
public
void
setContainer(Container container);
public
DefaultContext getDefaultContext();
public
void
setDefaultContext(DefaultContext defaultContext);
public
boolean
getDistributable();
public
void
setDistributable(
boolean
distributable);
public
String getInfo();
public
int
getMaxInactiveInterval();
public
void
setMaxInactiveInterval(
int
interval);
public
void
add(Session session);
public
void
addPropertyChangeListener(PropertyChangeListener listener);
public
Session createSession();
public
Session findSession(String id)
throws
IOException;
public
Session[] findSessions();
public
void
load()
throws
ClassNotFoundException, IOException;
public
void
remove(Session session);
public
void
removePropertyChangeListener(PropertyChangeListener listener);
public
void
unload()
throws
IOException;
}
提供了setContainer()方法使用session管理器與Context容器相關聯;一起一下添加、移除session實例的方法;設置session對象的最長存活時間;最后, load()方法與unload()方法用于從輔助存儲器加載session對象到內存和序列化session對象并持久化到輔助存儲器中。
ManagerBase類為一個抽象類,提供了一些公共方法的實現,包括創建session對象、移除session對象等;這些活動的session對象都存儲在一個名為sessions的HashMap變量中
protected HashMap sessions = new HashMap();
下面是創建session實例的方法
public
Session createSession() {
//
Recycle or create a Session instance
Session session =
null
;
synchronized
(recycled) {
int
size =
recycled.size();
if
(size > 0
) {
session
= (Session) recycled.get(size - 1
);
recycled.remove(size
- 1
);
}
}
if
(session !=
null
)
session.setManager(
this
);
else
session
=
new
StandardSession(
this
);
//
Initialize the properties of the new session and return it
session.setNew(
true
);
session.setValid(
true
);
session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval(
this
.maxInactiveInterval);
String sessionId
=
generateSessionId();
String jvmRoute
=
getJvmRoute();
//
@todo Move appending of jvmRoute generateSessionId()???
if
(jvmRoute !=
null
) {
sessionId
+= '.' +
jvmRoute;
session.setId(sessionId);
}
/*
synchronized (sessions) {
while (sessions.get(sessionId) != null) // Guarantee uniqueness
sessionId = generateSessionId();
}
*/
session.setId(sessionId);
return
(session);
}
上面創建的是StandardSession類型實例,在實現HttpRequest接口的HttpRequestBase類的相關方法中,調用getSession()方法返回StandardSessionFacade類型實例
創建session對象需要調用受保護的方法返回session對象的唯一標識符
/**
* Generate and return a new session identifier.
*/
protected
synchronized
String generateSessionId() {
//
Generate a byte array containing a session identifier
Random random =
getRandom();
byte
bytes[] =
new
byte
[SESSION_ID_BYTES];
getRandom().nextBytes(bytes);
bytes
=
getDigest().digest(bytes);
//
Render the result as a String of hexadecimal digits
StringBuffer result =
new
StringBuffer();
for
(
int
i = 0; i < bytes.length; i++
) {
byte
b1 = (
byte
) ((bytes[i] & 0xf0) >> 4
);
byte
b2 = (
byte
) (bytes[i] & 0x0f
);
if
(b1 < 10
)
result.append((
char
) ('0' +
b1));
else
result.append((
char
) ('A' + (b1 - 10
)));
if
(b2 < 10
)
result.append((
char
) ('0' +
b2));
else
result.append((
char
) ('A' + (b2 - 10
)));
}
return
(result.toString());
}
該標識符生成之后,會發送到客戶端cookies里面,使客戶端cookies里面的 JSESSIONID 值與之一致
其他相關操作session對象方法如下,容易理解
public
void
add(Session session) {
synchronized
(sessions) {
sessions.put(session.getId(), session);
}
}
public
Session findSession(String id)
throws
IOException {
if
(id ==
null
)
return
(
null
);
synchronized
(sessions) {
Session session
=
(Session) sessions.get(id);
return
(session);
}
}
public
Session[] findSessions() {
Session results[]
=
null
;
synchronized
(sessions) {
results
=
new
Session[sessions.size()];
results
=
(Session[]) sessions.values().toArray(results);
}
return
(results);
}
public
void
remove(Session session) {
synchronized
(sessions) {
sessions.remove(session.getId());
}
}
StandardManager類是Manager接口的標準實現,繼承自上面的ManagerBase抽象類,同時實現了Lifecycle接口,這有可以由其相關聯的Context容器來啟動和關閉,在Context容器調用它的start()方法和stop()方法時,會調用load()從輔助存儲器加載session對象到內存和調用unload()方法從內存持久化session對象到輔助存儲器
同時session管理器還負責銷毀那些失效的session對象,這是由一個專門的線程來實現的,StandardManager類實現了Runnable接口
/**
* The background thread that checks for session timeouts and shutdown.
*/
public
void
run() {
//
Loop until the termination semaphore is set
while
(!
threadDone) {
threadSleep();
processExpires();
}
}
在線程休眠指定時間間隔后,調用processExpires()方法清理過期session對象
/**
* Invalidate all sessions that have expired.
*/
private
void
processExpires() {
long
timeNow =
System.currentTimeMillis();
Session sessions[]
=
findSessions();
for
(
int
i = 0; i < sessions.length; i++
) {
StandardSession session
=
(StandardSession) sessions[i];
if
(!
session.isValid())
continue
;
int
maxInactiveInterval =
session.getMaxInactiveInterval();
if
(maxInactiveInterval < 0
)
continue
;
int
timeIdle =
//
Truncate, do not round up
(
int
) ((timeNow - session.getLastAccessedTime()) / 1000L
);
if
(timeIdle >=
maxInactiveInterval) {
try
{
session.expire();
}
catch
(Throwable t) {
log(sm.getString(
"standardManager.expireException"
), t);
}
}
}
}
我們可以看到,session對象的過期時間實際是session對象本身的成員變量的值
int maxInactiveInterval = session.getMaxInactiveInterval()
最后,servlet程序員需要調用javax.servlet.http.HttpSerletRequest接口的getSession()方法獲取Session對象,當調用getSession()方法時,request對象必須調用與Context容器相關聯的session管理器(創建session對象或返回一個已存在色session對象);request對象為了能夠訪問Session管理器,它必須能夠訪問Context容器。因此在SimpleWrapperValve類的invoke()方法中,需要調用org.apache.catalina.Request接口的setContext()方法傳入Context容器實例
public
void
invoke(Request request, Response response, ValveContext valveContext)
throws
IOException, ServletException {
SimpleWrapper wrapper
=
(SimpleWrapper) getContainer();
ServletRequest sreq
=
request.getRequest();
ServletResponse sres
=
response.getResponse();
Servlet servlet
=
null
;
HttpServletRequest hreq
=
null
;
if
(sreq
instanceof
HttpServletRequest)
hreq
=
(HttpServletRequest) sreq;
HttpServletResponse hres
=
null
;
if
(sres
instanceof
HttpServletResponse)
hres
=
(HttpServletResponse) sres;
//
-- new addition -----------------------------------
Context context = (Context) wrapper.getParent();
request.setContext(context);
//
-------------------------------------
//
Allocate a servlet instance to process this request
try
{
servlet
=
wrapper.allocate();
if
(hres!=
null
&& hreq!=
null
) {
servlet.service(hreq, hres);
}
else
{
servlet.service(sreq, sres);
}
}
catch
(ServletException e) {
}
}
同時在org.apache.catalina.connector.HttpRequestBase類的私有方法diGetSession()里面會調用Context接口的getManager()方法來獲取session管理器對象
private
HttpSession 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.getSession());
//
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
) {
return
(session.getSession());
}
}
//
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(
"httpRequestBase.createCommitted"
));
}
session
=
manager.createSession();
if
(session !=
null
)
return
(session.getSession());
else
return
(
null
);
}
注意里面實質是通過StandardSession實例的getSession()方法獲取一個經過外觀類StandardSessionFacade包裝的HttpSession對象
---------------------------------------------------------------------------?
本系列How Tomcat Works系本人原創?
轉載請注明出處 博客園 刺猬的溫馴?
本人郵箱: ? chenying998179 # 163.com ( #改為@ )
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

