tomcat容器通過一個稱為Session管理器的組件來管理建立的Session對象,該組件由org.apache.catalina.Manager接口表示;Session管理器必須與一個Context容器相關(guān)聯(lián)(需要用到Context容器的相關(guān)上下文或方法)。
默認情況下,Session管理器會將其所管理的 Session對象存放在內(nèi)存中,不過在tomcat中,Session管理器也庫將Session對象持久化,存儲到文件存儲器或通過JDBC寫入到數(shù)據(jù)庫中。
下面我們來分析具體實現(xiàn),在servlet編程方面中,Session對象由javax.servlet.http.HttpSession接口表示;在tomcat中該接口的標準實現(xiàn)是org.apache.catalina.session包下的StandardSession類,該類同時實現(xiàn)了org.apache.catalina.Session接口,在tomcat內(nèi)部供Session管理器使用;而實際交給servlet實例使用的是Session接口的外觀類StandardSessionFacade
下面是Session管理器內(nèi)部使用的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管理器相關(guān)聯(lián);Session管理器可以通過setId()方法設(shè)置Session標識符;同時會調(diào)用getLastAccessedTime()方法判斷一個Session對象的有效性;setValid()方法用于重置該Session對象的有效性;每當訪問一個Session實例時,會調(diào)用access()方法來修改Session對象的最后訪問時間;最后,Session管理器會調(diào)用Session對象的expire()方法使其過期,通過getSession()方法獲取一個經(jīng)過外觀類StandardSessionFacade包裝的HttpSession對象
StandardSession類是Session接口的標準實現(xiàn),同時實現(xiàn)了javax.servlet.http.HttpSession接口和java.lang.Serializable接口
(注:StandardSession類實現(xiàn)HttpSession接口方法的實現(xiàn)基本上都依賴于實現(xiàn)Session接口的方法對StandardSession實例的填充,因此我們可以想象,StandardSession類基本上類似與適配器模式中的Adapter角色,實現(xiàn)了原類型(Session接口類型)到目標接口的轉(zhuǎn)換(HttpSession接口))
其構(gòu)造函數(shù)接受一個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 ; // 創(chuàng)建時間 private long creationTime = 0L ; // 是否過期 private transient boolean expiring = false ; // 外觀類 private transient StandardSessionFacade facade = null ; // session標識 private String id = null ; // 最后訪問時間 private long lastAccessedTime = creationTime; // 監(jiān)聽器聚集 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;
其中g(shù)etSession()方法通過傳入自身實例來創(chuàng)建外觀類StandardSessionFacade實例
public HttpSession getSession() { if (facade == null ) facade = new StandardSessionFacade( this ); return (facade); }
如果session管理器中的某個Session對象在某個時間長度內(nèi)都沒有被訪問的話,會被Session管理器設(shè)置為過期,這個時間長度是由變量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實例并觸發(fā)一些事件,同時設(shè)置一些內(nèi)部變量的值。
為了傳遞一個session對象給servlet實例,tomcat的session管理器會實例化StandardSession類,并填充該session對象;不過,為了阻止servlet程序員訪問StandardSession實例中的一些敏感方法,tomcat容器將StandardSession實例封裝為StandardSessionFacade類型的實例(實現(xiàn)HttpRequest接口的HttpRequestBase類的doGetSession方法里面調(diào)用session管理器的createSession()方法返回StandardSession類的實例,然后調(diào)用StandardSession實例 的getSession()方法返回StandardSessionFacade類型實例),該類僅僅實現(xiàn)了javax.servlet.http.HttpSession接口中的方法,這樣servlet程序員就不能將HttpSession對象向下轉(zhuǎn)型為StandardSession類型
下面接下來描述session管理器,session管理器是org.apache.catalina.Manager接口的實例,抽象類ManagerBase實現(xiàn)了Manager接口,提供了常見功能的實現(xiàn);ManagerBase類有兩個直接子類,分別為StandardManager類和PersistentManagerBase類
當tomcat運行時,StandardManager實例將session對象存儲在內(nèi)存中;當tomcat關(guān)閉時,它會將當前內(nèi)存中所有的session對象序列化存儲到文件中;當在此運行tomcat時,又會將這些session對象重新載入內(nèi)存。
另外,繼承自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容器相關(guān)聯(lián);一起一下添加、移除session實例的方法;設(shè)置session對象的最長存活時間;最后, load()方法與unload()方法用于從輔助存儲器加載session對象到內(nèi)存和序列化session對象并持久化到輔助存儲器中。
ManagerBase類為一個抽象類,提供了一些公共方法的實現(xiàn),包括創(chuàng)建session對象、移除session對象等;這些活動的session對象都存儲在一個名為sessions的HashMap變量中
protected HashMap sessions = new HashMap();
下面是創(chuàng)建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); }
上面創(chuàng)建的是StandardSession類型實例,在實現(xiàn)HttpRequest接口的HttpRequestBase類的相關(guān)方法中,調(diào)用getSession()方法返回StandardSessionFacade類型實例
創(chuàng)建session對象需要調(diào)用受保護的方法返回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()); }
該標識符生成之后,會發(fā)送到客戶端cookies里面,使客戶端cookies里面的 JSESSIONID 值與之一致
其他相關(guān)操作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接口的標準實現(xiàn),繼承自上面的ManagerBase抽象類,同時實現(xiàn)了Lifecycle接口,這有可以由其相關(guān)聯(lián)的Context容器來啟動和關(guān)閉,在Context容器調(diào)用它的start()方法和stop()方法時,會調(diào)用load()從輔助存儲器加載session對象到內(nèi)存和調(diào)用unload()方法從內(nèi)存持久化session對象到輔助存儲器
同時session管理器還負責銷毀那些失效的session對象,這是由一個專門的線程來實現(xiàn)的,StandardManager類實現(xiàn)了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(); } }
在線程休眠指定時間間隔后,調(diào)用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程序員需要調(diào)用javax.servlet.http.HttpSerletRequest接口的getSession()方法獲取Session對象,當調(diào)用getSession()方法時,request對象必須調(diào)用與Context容器相關(guān)聯(lián)的session管理器(創(chuàng)建session對象或返回一個已存在色session對象);request對象為了能夠訪問Session管理器,它必須能夠訪問Context容器。因此在SimpleWrapperValve類的invoke()方法中,需要調(diào)用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()里面會調(diào)用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 ); }
注意里面實質(zhì)是通過StandardSession實例的getSession()方法獲取一個經(jīng)過外觀類StandardSessionFacade包裝的HttpSession對象
---------------------------------------------------------------------------?
本系列How Tomcat Works系本人原創(chuàng)?
轉(zhuǎn)載請注明出處 博客園 刺猬的溫馴?
本人郵箱: ? chenying998179 # 163.com ( #改為@ )
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
