org.apache.catalina.connector.Request的getSession方法说起
public HttpSession getSession() {
Session session = doGetSession(true);//如果没有找到session默认情况下创建新session
if (session == null) {
return null;
}
return session.getSession();//这里返回一个StandardSessionFacade对象,主要是因为StandardSession不仅实现了HttpSession还实现了Session接口,如果返回StandardSession对象,那就有可能被用户使用session接口中的方法,为了避免这种情况,为了安全,就返回StandardSessionFacade,他只实现了HttpSession接口。
}
protected Session doGetSession(boolean create) {
// There cannot be a session if no context has been assigned yet
Context context = getContext();
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);//估计使用了forward方法,导致重复使用了request
}
// Return the requested session if it exists and is valid
Manager manager = context.getManager();//返回session管理器
if (manager == null) {
return null; // Sessions are not supported
}
if (requestedSessionId != null) {//不为null说明http请求报文头中带有sessionID
try {
session = manager.findSession(requestedSessionId);//在manager管理的sessions(ConcurrentHashMap)中根据sessionid查找session对象
} 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 ((response != null) &&
context.getServletContext().getEffectiveSessionTrackingModes().
contains(SessionTrackingMode.COOKIE) &&
response.getResponse().isCommitted()) {//没看懂为啥,估计是因为response已经输出,不能再获取request的session了。
throw new IllegalStateException
(sm.getString("coyoteRequest.sessionCreateCommitted"));
}
// Re-use session IDs provided by the client in very limited
// circumstances.
String sessionId = getRequestedSessionId();
if (requestedSessionSSL) {
// If the session ID has been obtained from the SSL handshake then
// use it.
} else if (("/".equals(context.getSessionCookiePath())//这种情况是cookie在同一个host下多个应用程序都可见
&& isRequestedSessionIdFromCookie())) {
/* This is the common(ish) use case: using the same session ID with
* multiple web applications on the same host. Typically this is
* used by Portlet implementations. It only works if sessions are
* tracked via cookies. The cookie must have a path of "/" else it
* won't be provided for requests to all web applications.
*
* Any session ID provided by the client should be for a session
* that already exists somewhere on the host. Check if the context
* is configured for this to be confirmed.
*/
if (context.getValidateClientProvidedNewSessionId()) {
boolean found = false;
for (Container container : getHost().findChildren()) {
Manager m = ((Context) container).getManager();
if (m != null) {
try {
if (m.findSession(sessionId) != null) {
found = true;
break;
}
} catch (IOException e) {
// Ignore. Problems with this manager will be
// handled elsewhere.
}
}
}
if (!found) {
sessionId = null;
}
}
} else {
sessionId = null;
}
session = manager.createSession(sessionId);
// Creating a new session cookie based on that session
if ((session != null) && (getContext() != null)
&& getContext().getServletContext().
getEffectiveSessionTrackingModes().contains(
SessionTrackingMode.COOKIE)) {
Cookie cookie =
ApplicationSessionCookieConfig.createSessionCookie(
context, session.getIdInternal(), isSecure());
response.addSessionCookieInternal(cookie);
}
if (session == null) {
return null;
}
session.access();
return session;
}
至于session的持久化是由线程完成的,这个线程不仅会持久化session,还会实现热加载,如下:
ContainerBase.threadStart
protected void threadStart() {
if (thread != null)
return;
if (backgroundProcessorDelay <= 0) //从代码来看只有StandardEngine给他赋值为10,所以只有StandardEngine这个container能往下执行,所以本来是一个container一个线程,现在来看只有StandardEngine能往下走,所以就这一个线程了,不过已经足够了。
return;
threadDone = false;
String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
thread = new Thread(new ContainerBackgroundProcessor(), threadName);
thread.setDaemon(true);
thread.start();
}
略去一下代码后 会调用到container.backgroundProcess();
有些container比如context和wrapper里有自己的backgroundProcess,有的没有自己的,就用ContainerBase中的
public void backgroundProcess() {
if (!getState().isAvailable())
return;
if (cluster != null) {
try {
cluster.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.cluster", cluster), e);
}
}
if (loader != null) {
try {
loader.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.loader", loader), e);
}
}
if (manager != null) {
try {
manager.backgroundProcess();//这里就是持久化session的代码,但后续还会需要判断一下条件来决定是否持久化session,比如maxIdleBackup
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.manager", manager), e);
}
}
Realm realm = getRealmInternal();
if (realm != null) {
try {
realm.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.realm", realm), e);
}
}
Valve current = pipeline.getFirst();
while (current != null) {
try {
current.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.valve", current), e);
}
current = current.getNext();
}
fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
}