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); 
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值