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 ( #改为@ )

