tomcat處理session(轉載)

Session對象的創建一般是源於這樣的一條語句:
Session session = request.getSession(false);或者Session session = request.getSession();如果不在乎服務器壓力可能多那麼一點點的話。

在Tomcat的實現中,這個request是org.apache.catalina.connector.Request類的包裝類org.apache.catalina.connector.RequestFacade的對象,它的兩個#getSession()方法如下:

Java代碼 
public HttpSession getSession(boolean create) { 
    if (request == null) { 
        throw new IllegalStateException( 
                        sm.getString("requestFacade.nullRequest")); 
    } 
 
    if (SecurityUtil.isPackageProtectionEnabled()){ 
        return (HttpSession)AccessController. 
            doPrivileged(new GetSessionPrivilegedAction(create)); 
    } else { 
        return request.getSession(create); 
    } 


Java代碼 
public HttpSession getSession() { 
    if (request == null) { 
        throw new IllegalStateException( 
                        sm.getString("requestFacade.nullRequest")); 
    } 
 
    return getSession(true); 


其實差不太多,最後都會進入org.apache.catalina.connector.Request的#getSession()方法。這個方法的源代碼如下:

Java代碼 
public HttpSession getSession(boolean create) { 
    Session session = doGetSession(create); 
    if (session != null) { 
        return session.getSession(); 
    } else { 
        return null; 
    } 


然後調用就到了#doGetSession()這個方法了。源代碼如下

Java代碼 
protected Session doGetSession(boolean create) { 
    // 沒有Context的話直接返回null 
    if (context == null) 
        return (null); 
 
    // 判斷Session是否有效 
    if ((session != null) && !session.isValid()) 
        session = null; 
    if (session != null) 
        return (session); 
 
    // 返回Manager對象,這裏是StandardManager類的對象 
    Manager manager = null; 
    if (context != null) 
        manager = context.getManager(); 
    if (manager == null) 
        return (null); // Sessions are not supported 
    // 判斷是否有SessionID 
    if (requestedSessionId != null) { 
        try { 
            // 在Manager中根據SessionID查找Session 
            session = manager.findSession(requestedSessionId); 
        } catch (IOException e) { 
            session = null; 
        } 
        if ((session != null) && !session.isValid()) 
            session = null; 
        if (session != null) { 
            // 更新訪問時間 
            session.access(); 
            return (session); 
        } 
    } 
 
    // 創建新的Session 
    if (!create) 
        return (null); 
    if ((context != null) && (response != null) && context.getCookies() 
            && response.getResponse().isCommitted()) { 
        throw new IllegalStateException(sm.getString("coyoteRequest.sessionCreateCommitted")); 
    } 
 
    // 判斷是否使用 "/" 作爲Session Cookie的存儲路徑 並且 是否SessionID來自Cookie 
    if (connector.getEmptySessionPath() && isRequestedSessionIdFromCookie()) { 
        // 創建Session 
        session = manager.createSession(getRequestedSessionId()); 
    } else { 
        session = manager.createSession(null); 
    } 
 
    // 創建一個新的Session Cookies 
    if ((session != null) && (getContext() != null) && getContext().getCookies()) { 
        Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME, session.getIdInternal()); 
        // 配置Session Cookie 
        configureSessionCookie(cookie); 
        // 在響應中加入Session Cookie 
        response.addCookieInternal(cookie); 
    } 
 
    if (session != null) { 
        // 更新訪問時間 
        session.access(); 
        return (session); 
    } else { 
        return (null); 
    } 
 

這個方法說明了Session創建的大致過程,首先判斷requestedSessionId是否存在,如果存在,那麼根據這個ID去查找Session對象。如果requestedSessionId不存在或者沒有取到Session,並且傳遞給#getSession(boolean)的參數爲真,那麼要創建一個新的Session,並且給客戶端寫回去一個Session Cookie。

首先,我感興趣的是requestedSessionId的賦值,它到底是什麼時候被賦值的呢?

還要向回看Tomcat的請求處理過程,請求曾到過這一步,org.apache.catalina.connector.CoyoteAdapter的#service()方法。裏邊有這樣一句方法調用:postParseRequest(req, request, res, response)。就是這一步處理了SessionID的獲取,這個方法調用了#parseSessionId()和parseSessionCookiesId()這兩個方法,就是它對Session ID進行了提取,源代碼分別如下:

Java代碼 
protected void parseSessionId(org.apache.coyote.Request req, Request request) { 
 
    ByteChunk uriBC = req.requestURI().getByteChunk(); 
    // 判斷URL中是不是有";jsessionid="這個字符串 
    int semicolon = uriBC.indexOf(match, 0, match.length(), 0); 
 
    if (semicolon > 0) { 
        // Parse session ID, and extract it from the decoded request URI 
        // 在URL中提取Session ID 
        int start = uriBC.getStart(); 
        int end = uriBC.getEnd(); 
 
        int sessionIdStart = semicolon + match.length(); 
        int semicolon2 = uriBC.indexOf(';', sessionIdStart); 
        if (semicolon2 >= 0) { 
            request.setRequestedSessionId(new String(uriBC.getBuffer(), start + sessionIdStart, 
                    semicolon2 - sessionIdStart)); 
            byte[] buf = uriBC.getBuffer(); 
            for (int i = 0; i < end - start - semicolon2; i++) { 
                buf[start + semicolon + i] = buf[start + i + semicolon2]; 
            } 
            uriBC.setBytes(buf, start, end - start - semicolon2 + semicolon); 
        } else { 
            request.setRequestedSessionId(new String(uriBC.getBuffer(), start + sessionIdStart, 
                    (end - start) - sessionIdStart)); 
            uriBC.setEnd(start + semicolon); 
        } 
        // 設定Session ID來自於URL 
        request.setRequestedSessionURL(true); 
 
    } else { 
        request.setRequestedSessionId(null); 
        request.setRequestedSessionURL(false); 
    } 
 


Java代碼 
protected void parseSessionCookiesId(org.apache.coyote.Request req, Request request) { 
    Context context = (Context) request.getMappingData().context; 
    if (context != null && !context.getCookies()) 
        return; 
 
    // 返回Cookie 
    Cookies serverCookies = req.getCookies(); 
    int count = serverCookies.getCookieCount(); 
    if (count <= 0) 
        return; 
 
    for (int i = 0; i < count; i++) { 
        ServerCookie scookie = serverCookies.getCookie(i); 
        // 判斷是否有JSESSIONID這個名字的Cookie 
        if (scookie.getName().equals(Globals.SESSION_COOKIE_NAME)) { 
            // Override anything requested in the URL 
            if (!request.isRequestedSessionIdFromCookie()) { 
                // 設定Session ID 
                convertMB(scookie.getValue()); 
                request.setRequestedSessionId(scookie.getValue().toString()); 
                // 如果之前在URL中讀到了SessionID,那麼會覆蓋它 
                request.setRequestedSessionCookie(true); 
                request.setRequestedSessionURL(false); 
                if (log.isDebugEnabled()) 
                    log.debug(" Requested cookie session id is " + request.getRequestedSessionId()); 
            } else { 
                if (!request.isRequestedSessionIdValid()) { 
                    convertMB(scookie.getValue()); 
                    request.setRequestedSessionId(scookie.getValue().toString()); 
                } 
            } 
        } 
    } 
 

Tomcat就是通過上邊的兩個方法讀到URL或者Cookie中存放的Session ID的。

瞭解了Session ID的獲取,下面要看一下Session的查找過程,就是org.apache.catalina.session.StandardManager的#findSession()方法。這個方法是在它的基類中定義的,源代碼如下:

Java代碼 
public Session findSession(String id) throws IOException { 
    if (id == null) 
        return (null); 
    return (Session) sessions.get(id); 

代碼很短,其中sessions是一個ConcurrentHashMap<String, Session>對象。那麼這個sessions的對象是什麼時候載入的Session呢?

啓動的時候!可以看一下StandardManager#start()方法。最後調用了#load()方法,這個就是載入Session的方法了:

Java代碼 
public void load() throws ClassNotFoundException, IOException { 
    if (SecurityUtil.isPackageProtectionEnabled()) { 
        try { 
            AccessController.doPrivileged(new PrivilegedDoLoad()); 
        } catch (PrivilegedActionException ex) { 
            Exception exception = ex.getException(); 
            if (exception instanceof ClassNotFoundException) { 
                throw (ClassNotFoundException) exception; 
            } else if (exception instanceof IOException) { 
                throw (IOException) exception; 
            } 
            if (log.isDebugEnabled()) 
                log.debug("Unreported exception in load() " + exception); 
        } 
    } else { 
        doLoad(); 
    } 

最後調用了#doLoad()方法來具體的載入Session,源代碼如下:

Java代碼 
protected void doLoad() throws ClassNotFoundException, IOException { 
    if (log.isDebugEnabled()) 
        log.debug("Start: Loading persisted sessions"); 
 
    // 清空Map 
    sessions.clear(); 
 
    // 對應work/Catalina/localhost/%app name%/SESSIONS.ser文件 
    File file = file(); 
    if (file == null) 
        return; 
    if (log.isDebugEnabled()) 
        log.debug(sm.getString("standardManager.loading", pathname)); 
    FileInputStream fis = null; 
    ObjectInputStream ois = null; 
    Loader loader = null; 
    ClassLoader classLoader = null; 
    try { 
        // 載入Session緩存文件 
        fis = new FileInputStream(file.getAbsolutePath()); 
        BufferedInputStream bis = new BufferedInputStream(fis); 
        if (container != null) 
            loader = container.getLoader(); 
        if (loader != null) 
            classLoader = loader.getClassLoader(); 
        if (classLoader != null) { 
            if (log.isDebugEnabled()) 
                log.debug("Creating custom object input stream for class loader "); 
            ois = new CustomObjectInputStream(bis, classLoader); 
        } else { 
            if (log.isDebugEnabled()) 
                log.debug("Creating standard object input stream"); 
            ois = new ObjectInputStream(bis); 
        } 
    } catch (FileNotFoundException e) { 
        if (log.isDebugEnabled()) 
            log.debug("No persisted data file found"); 
        return; 
    } catch (IOException e) { 
        log.error(sm.getString("standardManager.loading.ioe", e), e); 
        if (ois != null) { 
            try { 
                ois.close(); 
            } catch (IOException f) { 
                ; 
            } 
            ois = null; 
        } 
        throw e; 
    } 
 
    synchronized (sessions) { 
        try { 
            // 讀出Session個數 
            Integer count = (Integer) ois.readObject(); 
            int n = count.intValue(); 
            if (log.isDebugEnabled()) 
                log.debug("Loading " + n + " persisted sessions"); 
            //  讀入Session 
            for (int i = 0; i < n; i++) { 
                StandardSession session = getNewSession(); 
                session.readObjectData(ois); 
                session.setManager(this); 
                sessions.put(session.getIdInternal(), session); 
                session.activate(); 
                sessionCounter++; 
            } 
        } catch (ClassNotFoundException e) { 
            log.error(sm.getString("standardManager.loading.cnfe", e), e); 
            if (ois != null) { 
                try { 
                    ois.close(); 
                } catch (IOException f) { 
                    ; 
                } 
                ois = null; 
            } 
            throw e; 
        } catch (IOException e) { 
            log.error(sm.getString("standardManager.loading.ioe", e), e); 
            if (ois != null) { 
                try { 
                    ois.close(); 
                } catch (IOException f) { 
                    ; 
                } 
                ois = null; 
            } 
            throw e; 
        } finally { 
            try { 
                if (ois != null) 
                    ois.close(); 
            } catch (IOException f) { 
            } 
 
            // 刪除Session緩存文件 
            if (file != null && file.exists()) 
                file.delete(); 
        } 
    } 
 
    if (log.isDebugEnabled()) 
        log.debug("Finish: Loading persisted sessions"); 

大致知道了Session的讀取過程,後面就是Session沒找到時創建Session的過程了。具體就是org.apache.catalina.session.StandardManager的#createSession()方法:

Java代碼 
public Session createSession(String sessionId) { 
    if ((maxActiveSessions >= 0) && (sessions.size() >= maxActiveSessions)) { 
        rejectedSessions++; 
        throw new IllegalStateException(sm.getString("standardManager.createSession.ise")); 
    } 
    return (super.createSession(sessionId)); 

最後調用到了它的基類的#createSession()方法了。

Java代碼 
public Session createSession(String sessionId) { 
    // 創建一個新的Session 
    Session session = createEmptySession(); 
 
    // 初始化Session的屬性 
    session.setNew(true); 
    session.setValid(true); 
    session.setCreationTime(System.currentTimeMillis()); 
    session.setMaxInactiveInterval(this.maxInactiveInterval); 
    // 如果Session ID爲null,那麼就生成一個 
    if (sessionId == null) { 
        sessionId = generateSessionId(); 
    } 
    session.setId(sessionId); 
    sessionCounter++; 
 
    return (session); 
 

通過上述過程,一個新的Session就創建出來了。

 

tomcat會開啓一個後臺線程每隔一段時間檢查Session的有效性,這個線程是在Tomcat啓動的時候當StardardEngine啓動時隨之啓動的。可以參看StardardEngine的基類ContainerBase的#threadStart()方法:

Java代碼 
protected void threadStart() { 
    if (thread != null) 
        return; 
    if (backgroundProcessorDelay <= 0) 
        return; 
     
    threadDone = false; 
    String threadName = "ContainerBackgroundProcessor[" + toString() + "]"; 
    // 開啓後臺的監控線程 
    thread = new Thread(new ContainerBackgroundProcessor(), threadName); 
    thread.setDaemon(true); 
    thread.start(); 
 


這個方法啓動了一個ContainerBackgroundProcessor類的線程,這個類重寫的#run()方法中包括了對Session的有效性監控。具體的細節就不詳細陳述了。每隔一段時間,此線程就會啓動一次並調用了ManageBase的#backgroundProcess()方法。其源代碼如下:

Java代碼 
public void backgroundProcess() { 
    count = (count + 1) % processExpiresFrequency; 
    if (count == 0) 
        processExpires(); 


每隔一段時間就會調用processExpires()方法去判斷Session的有效性。

Java代碼 
public void processExpires() { 
    // 現在的時間 
    long timeNow = System.currentTimeMillis(); 
    // 所有的Session對象 
    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++) { 
        // 判斷並設定Session是否失效 
        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); 
 


此方法最終調用了isValid()去判斷和設定Session是否失效,源代碼如下所示:

Java代碼 
public boolean isValid() { 
    // 是否過期 
    if (this.expiring) { 
        return true; 
    } 
    // 是否有效 
    if (!this.isValid) { 
        return false; 
    } 
    // 正在使用中並且訪問數大於0 
    if (ACTIVITY_CHECK && accessCount.get() > 0) { 
        return true; 
    } 
 
    if (maxInactiveInterval >= 0) { 
        // 判斷Session是否過期 
        long timeNow = System.currentTimeMillis(); 
        int timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L); 
        if (timeIdle >= maxInactiveInterval) { 
            // 設定Session過期 
            expire(true); 
        } 
    } 
 
    return (this.isValid); 
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章