最近有空看了一下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会广播这个消息。