Tomcat源码学习--Session创建销毁

每个web后台开发人员肯定对Session有所接触和了解,简单来说就是后台服务器维护了一个存在有效时间和范围限制的缓存数据。

接下来我们通过这篇博客来分析一下tomcat创建、使用和销毁Session的相关过程。首先我们需要看一下tomcat中与session相关的所有类,如下:


HttpSession:是javax.servlet包中提供的session接口

StandardSessionFacade:HttpSession的实现类,StandardSession的外观类

Session:tomcat内部session接口

StandardSession:实现了Session和HttpSession,是tomcat维护session的载体

一、创建获取session

1、通过request获取session

HttpSession session  = request.getSession();
2、当session不存在是时创建新的session

tomcat实现了接口HttpServletRequest得的RequestFacade()

@Override
    public HttpSession getSession() {

        if (request == null) {
            throw new IllegalStateException(
                            sm.getString("requestFacade.nullRequest"));
        }
        return getSession(true);
    }
@Override
    public HttpSession getSession(boolean create) {

        if (request == null) {
            throw new IllegalStateException(
                            sm.getString("requestFacade.nullRequest"));
        }

        if (SecurityUtil.isPackageProtectionEnabled()){
            return AccessController.
                doPrivileged(new GetSessionPrivilegedAction(create));
        } else {
            return request.getSession(create);
        }
    }
在tomcat的Request类中获取session

 @Override
    public HttpSession getSession(boolean create) {
        Session session = doGetSession(create);
        if (session == null) {
            return null;
        }

        return session.getSession();
    }
在doGetSession中获取session,doGetSession会包括session的查找创建等操作。

protected Session doGetSession(boolean create) {

        // There cannot be a session if no context has been assigned yet
		//session的作用域范围是上下午
        Context context = getContext();
        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 = context.getManager();
        if (manager == null) {
            return (null);      // Sessions are not supported
        }
		//requestedSessionId是用来区分session的key值,如果存在则直接获取
        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 (response != null
                && context.getServletContext()
                        .getEffectiveSessionTrackingModes()
                        .contains(SessionTrackingMode.COOKIE)
                && response.getResponse().isCommitted()) {
            throw new IllegalStateException(
                    sm.getString("coyoteRequest.sessionCreateCommitted"));
        }

        // Re-use session IDs provided by the client in very limited
        // circumstances.
		//如果session为空或者失效就创建新的session id
        String sessionId = getRequestedSessionId();
        if (requestedSessionSSL) {
            // If the session ID has been obtained from the SSL handshake then
            // use it.
        } else if (("/".equals(context.getSessionCookiePath())
                && isRequestedSessionIdFromCookie())) {
            if (context.getValidateClientProvidedNewSessionId()) {
                boolean found = false;
                for (Container container : getHost().findChildren()) {
                    Manager m = ((Context) container).getManager();
                    if (m != null) {
                        try {
                            if (m.findSession(sessionId) != null) {
                                found = true;
                                break;
                            }
                        } catch (IOException e) {
                            // Ignore. Problems with this manager will be
                            // handled elsewhere.
                        }
                    }
                }
                if (!found) {
                    sessionId = null;
                }
            }
        } else {
            sessionId = null;
        }
		//通过sessionid创建新的session
        session = manager.createSession(sessionId);

        // Creating a new session cookie based on that session
		//将sessionid添加到cookie中,这样可以通过cookie获取到sessionid并根据sessionid来获取session
        if (session != null
                && context.getServletContext()
                        .getEffectiveSessionTrackingModes()
                        .contains(SessionTrackingMode.COOKIE)) {
            Cookie cookie =
                ApplicationSessionCookieConfig.createSessionCookie(
                        context, session.getIdInternal(), isSecure());

            response.addSessionCookieInternal(cookie);
        }

        if (session == null) {
            return null;
        }
		//返回session
        session.access();
        return session;
    }

session查找,当requestedSessionId不为空时会在管理器中查找session

 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);
            }
        }
在ManagerBase基类中提供了findSession方法

  @Override
    public Session findSession(String id) throws IOException {
        if (id == null) {
            return null;
        }
		//sessions为ConcurrentHashMap类型
        return sessions.get(id);
    }

session对象最终是保存在一个ConcurrentHashMap中

protected Map<String, Session> sessions = new ConcurrentHashMap<>();
当查找不到session存在时会创建新的session

session = manager.createSession(sessionId);
createSession中会根据sessionid创建一个新的
@Override
    public Session createSession(String sessionId) {

        if ((maxActiveSessions >= 0) &&
                (getActiveSessions() >= maxActiveSessions)) {
            rejectedSessions++;
            throw new TooManyActiveSessionsException(
                    sm.getString("managerBase.createSession.ise"),
                    maxActiveSessions);
        }

        // Recycle or create a Session instance
        Session session = createEmptySession();

        // Initialize the properties of the new session and return it
        session.setNew(true);
        session.setValid(true);
        session.setCreationTime(System.currentTimeMillis());
        session.setMaxInactiveInterval(getContext().getSessionTimeout() * 60);
        String id = sessionId;
        if (id == null) {
            id = generateSessionId();
        }
        session.setId(id);
        sessionCounter++;

        SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
        synchronized (sessionCreationTiming) {
            sessionCreationTiming.add(timing);
            sessionCreationTiming.poll();
        }
        return (session);

    }
创建新的session就是一个StandardSession对象

Session session = createEmptySession();
 @Override
    public Session createEmptySession() {
        return (getNewSession());
    }
 protected StandardSession getNewSession() {
        return new StandardSession(this);
    }
session id会通过cookie保存到浏览器中,名称就是我们经常见到的是JSESSIONID了。


二、Session清理
在tomcat运行中会起一个守护线程ContainerBackgroundProcessor来进行session的清理工作

守护线程的通过循环执行,每清理一次的时间间隔默认值是10秒,当然这个线程不仅仅会清理session,不同的Container容器类型所做的清理工作是不一样的。

protected class ContainerBackgroundProcessor implements Runnable {

        @Override
        public void run() {
            Throwable t = null;
            String unexpectedDeathMessage = sm.getString(
                    "containerBase.backgroundProcess.unexpectedThreadDeath",
                    Thread.currentThread().getName());
            try {
                while (!threadDone) {
                    try {
						//backgroundProcessorDelay默认值是10,每10秒清理一下session
                        Thread.sleep(backgroundProcessorDelay * 1000L);
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                    if (!threadDone) {
                        processChildren(ContainerBase.this);
                    }
                }
            } catch (RuntimeException|Error e) {
                t = e;
                throw e;
            } finally {
                if (!threadDone) {
                    log.error(unexpectedDeathMessage, t);
                }
            }
        }

        protected void processChildren(Container container) {
            ClassLoader originalClassLoader = null;

            try {
                if (container instanceof Context) {
                    Loader loader = ((Context) container).getLoader();
                    // Loader will be null for FailedContext instances
                    if (loader == null) {
                        return;
                    }
                    originalClassLoader = ((Context) container).bind(false, null);
                }
				//执行后台处理操作
                container.backgroundProcess();
                Container[] children = container.findChildren();
                for (int i = 0; i < children.length; i++) {
                    if (children[i].getBackgroundProcessorDelay() <= 0) {
                        processChildren(children[i]);
                    }
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error("Exception invoking periodic operation: ", t);
            } finally {
                if (container instanceof Context) {
                    ((Context) container).unbind(false, originalClassLoader);
               }
            }
        }
    }
会执行子容器的backgroudProcess()方法,最终会调用ManagerBase的backgroudProcess()方法。

 @Override
    public void backgroundProcess() {
        count = (count + 1) % processExpiresFrequency;//执行清理的时间间隔是6*10秒即1分钟
        if (count == 0)
            processExpires();
    }
在processExpires()中会获取Context下的所有session,并依次调用session.isValid方法来清理自身

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);
		//调用session的isValid方法来清理自身
        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.isValid方法会判断自身是否有效

	@Override
    public boolean isValid() {

        if (!this.isValid) {
            return false;
        }

        if (this.expiring) {
            return true;
        }

        if (ACTIVITY_CHECK && accessCount.get() > 0) {
            return true;
        }

        if (maxInactiveInterval > 0) {
            int timeIdle = (int) (getIdleTimeInternal() / 1000L);
            if (timeIdle >= maxInactiveInterval) {
                expire(true);
            }
        }

        return this.isValid;
    }
session.expire(boolean notify)会将自身设置为无效并从管理器Manager中删除

public void expire(boolean notify) {

        // Check to see if session has already been invalidated.
        // Do not check expiring at this point as expire should not return until
        // isValid is false
        if (!isValid)
            return;

        synchronized (this) {
            // Check again, now we are inside the sync so this code only runs once
            // Double check locking - isValid needs to be volatile
            // The check of expiring is to ensure that an infinite loop is not
            // entered as per bug 56339
            if (expiring || !isValid)
                return;

            if (manager == null)
                return;

            // Mark this session as "being expired"
            expiring = true;

            // Notify interested application event listeners
            // FIXME - Assumes we call listeners in reverse order
            Context context = manager.getContext();

            // The call to expire() may not have been triggered by the webapp.
            // Make sure the webapp's class loader is set when calling the
            // listeners
            if (notify) {
                ClassLoader oldContextClassLoader = null;
                try {
                    oldContextClassLoader = context.bind(Globals.IS_SECURITY_ENABLED, null);
                    Object listeners[] = context.getApplicationLifecycleListeners();
                    if (listeners != null && listeners.length > 0) {
                        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 {
                                context.fireContainerEvent("beforeSessionDestroyed",
                                        listener);
                                listener.sessionDestroyed(event);
                                context.fireContainerEvent("afterSessionDestroyed",
                                        listener);
                            } catch (Throwable t) {
                                ExceptionUtils.handleThrowable(t);
                                try {
                                    context.fireContainerEvent(
                                            "afterSessionDestroyed", listener);
                                } catch (Exception e) {
                                    // Ignore
                                }
                                manager.getContext().getLogger().error
                                    (sm.getString("standardSession.sessionEvent"), t);
                            }
                        }
                    }
                } finally {
                    context.unbind(Globals.IS_SECURITY_ENABLED, oldContextClassLoader);
                }
            }

            if (ACTIVITY_CHECK) {
                accessCount.set(0);
            }

            // Remove this session from our manager's active sessions
            manager.remove(this, true);

            // Notify interested session event listeners
            if (notify) {
                fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);
            }

            // Call the logout method
            if (principal instanceof TomcatPrincipal) {
                TomcatPrincipal gp = (TomcatPrincipal) principal;
                try {
                    gp.logout();
                } catch (Exception e) {
                    manager.getContext().getLogger().error(
                            sm.getString("standardSession.logoutfail"),
                            e);
                }
            }

            // We have completed expire of this session
            setValid(false);
            expiring = false;

            // Unbind any objects associated with this session
            String keys[] = keys();
            ClassLoader oldContextClassLoader = null;
            try {
                oldContextClassLoader = context.bind(Globals.IS_SECURITY_ENABLED, null);
                for (int i = 0; i < keys.length; i++) {
                    removeAttributeInternal(keys[i], notify);
                }
            } finally {
                context.unbind(Globals.IS_SECURITY_ENABLED, oldContextClassLoader);
            }
        }

    }










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值