最近有空看了一下tomcat 6源碼里面對session管理的實現,現在寫下來,以供后考,也希望能對對此感興趣的朋友
有所提示。
閑話少說,先貼一下tomcat6的component層次圖(此圖來自tomcat doc)
Server 就是一個servlet container | Service 包含一個或多個connector的組 | Engine servlet engine.最頂級的container. | / | --- Cluster --* | Host 第二級container | ------ / / Cluster Context(1-N) 第三級container. servlet context. 也就是一個應用。 | / | -- Manager 應用的session管理器 | / | -- DeltaManager | -- BackupManager | --------------------------- | / Channel / ----------------------------- / | / Interceptor_1 .. / | / Interceptor_N / ----------------------------- / | | | / Receiver Sender Membership / -- Valve | / | -- ReplicationValve | -- JvmRouteBinderValve | -- LifecycleListener | -- ClusterListener | / | -- ClusterSessionListener | -- JvmRouteSessionIDBinderListener | -- Deployer / -- FarmWarDeployer
OK,基本層級關系說過了,就開始分析session的管理。
1. session 的創建。
更正:下面這段我理解錯了。其實進一步看下來,發現其實session的創建是在每個context里面。具體的創建可以看我
這篇之后的那篇文章. Session的生成實際是在調用最終處理的servlet的時候生成的。
session的創建的入口是在Host里面,每次request進來之后,會沿著container 的pipeline一直往下進行、
處理,順序是:engine->host->context->wrapper. 而pipeline的作用就是調用對應級別的value對
request和response進行處理。在StandardHostValue里面首先出現對session的調用:
java代碼
public final void invoke(Request request, Response response) throws IOException, ServletException { // Select the Context to be used for this Request Context context = request.getContext(); if (context == null) { response.sendError (HttpServletResponse.SC_INTERNAL_SERVER_ERROR, sm.getString("standardHost.noContext")); return; } // Bind the context CL to the current thread if( context.getLoader() != null ) { // Not started - it should check for availability first // This should eventually move to Engine, it's generic. Thread.currentThread().setContextClassLoader (context.getLoader().getClassLoader()); } // Ask this Context to PRocess this request context.getPipeline().getFirst().invoke(request, response); // access a session (if present) to update last accessed time, based on a // strict interpretation of the specification if (Globals.STRICT_SERVLET_COMPLIANCE) { request.getSession(false); } // Error page processing response.setSuspended(false); Throwable t = (Throwable) request.getAttribute(Globals.EXCEPTION_ATTR); if (t != null) { throwable(request, response, t); } else { status(request, response); } // Restore the context classloader Thread.currentThread().setContextClassLoader (StandardHostValve.class.getClassLoader()); }
注意里面的:request.getSession(false) 這一句。這一句最終會調用如下代碼;
Java代碼
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()) { Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME, session.getIdInternal()); configureSessionCookie(cookie); response.addCookieInternal(cookie, context.getUseHttpOnly()); } if (session != null) { session.access(); return (session); } else { return (null); } }
通過以上代碼,container或者返回一個已存在的session,或者新建一個session,或者返回Null(特殊情況).
而session的創建是由manager完成的。Manager接口的實現根據具體情況有很多種,比如:
StandardManager:默認的單機環境tomcat session manager.創建standardsession.
DeltaSessionManager:適用于集群環境。創建DeltaSession
....
不同的Manager管理不同具體類型的session,從而使得tomcat能夠很好的支持集群環境下面的session復制,持久化 等等。比如DeltaSession創建session的時候,會向集群中的其他節點群播一個信息。
2. session的管理。
大家都知道tomcat的session需要不斷使超時的session失效,那么這個共是怎么實現的呢?
在StandardContext啟動的時候哦,會同時啟動一個thread. 這個線程是一個daemon線程,
會定時調用ManagerBase的一下方法:
Java代碼
public void processExpires() { long timeNow = System.currentTimeMillis(); Session sessions[] = findSessions(); int expireHere = 0 ; if(log.isDebugEnabled()) log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length); for (int i = 0; i < sessions.length; i++) { if (sessions[i]!=null && !sessions[i].isValid()) { expireHere++; } } long timeEnd = System.currentTimeMillis(); if(log.isDebugEnabled()) log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere); processingTime += ( timeEnd - timeNow ); }
很顯然,session的有效性管理也通過session具體實現的。比如DeltaSession:
Java代碼
public void expire(boolean notify, boolean notifyCluster) { if (expiring) return; String expiredId = getIdInternal(); if(expiredId != null && manager != null && manager instanceof DeltaManager) { DeltaManager dmanager = (DeltaManager)manager; CatalinaCluster cluster = dmanager.getCluster(); ClusterMessage msg = dmanager.requestCompleted(expiredId, true); if (msg != null) { if(dmanager.doDomainReplication()) { cluster.sendClusterDomain(msg); } else { cluster.send(msg); } } } super.expire(notify); if (notifyCluster) { if (log.isDebugEnabled()) log.debug(sm.getString("deltaSession.notifying", ((ClusterManager)manager).getName(), new Boolean(isPrimarySession()), expiredId)); if ( manager instanceof DeltaManager ) { ( (DeltaManager) manager).sessionExpired(expiredId); } } }
可以看出,當session失效的時候,manager會廣播這個消息。
新聞熱點
疑難解答