转载

Tomcat的session管理探究

我有一个项目需要模拟 HttpSession ,在参考Tomcat的 HttpSession 管理时有一点心得,在这里记录一下。

先说说这几个关键类:

  1. org.apache.catalina.session.StandardManager : 管理Session的类

  2. org.apache.catalina.session.StandardSession : HttpSession 的实现

  3. org.apache.catalina.connector.Request : HttpServletRequest 的实现

StandardManager

下面介绍一下和Session相关的几个关键属性,以及方法

processExpiresFrequency

每隔多少次 StandardManager.backgroundProcess 做一次session清理,数字越小越频繁,默认6次。

下面是源代码片段:

/**   * Frequency of the session expiration, and related manager operations.   * Manager operations will be done once for the specified amount of   * backgrondProcess calls (ie, the lower the amount, the most often the   * checks will occur).   */ protected int processExpiresFrequency = 6;

StandardManager.backgroundProcess 的调用链是这样的:

  1. ContainerBase 里有一个 ContainerBackgroundProcessor 线程实例,

    这个线程会每隔 ContainerBase.backgroundProcessorDelay 的时间调用-->

  2. ContainerBase.processChildren ,这个方法调用-->

  3. ContainerBase.backgroundProcess ,这个方法调用-->

  4. StandardManager.backgroundProcess ,这个方法调用-->

  5. StandardManager.processExpires ,在这里清理掉已经过期的Session。

maxInactiveInterval

一个session不被访问的时间间隔,默认30分钟(1800秒)。

下面是源代码片段:

/**   * The default maximum inactive interval for Sessions created by   * this Manager.   */ protected int maxInactiveInterval = 30 * 60;

StandardManager.maxInactiveInterval 的值会作为新Session的默认 maxInactiveInterval 的值

(实际上用户在get到session后修改这个值)。

下面是代码片段:

public Session createSession(String sessionId) {   // ...    // 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(this.maxInactiveInterval);    // ... }

StandardSession

access()

StandardSession.access 方法是用来设置这个Session被访问的时间的,何时被调用会在 Request 里讲。

下面是代码片段:

/** * Update the accessed time information for this session.  This method * should be called by the context when a request comes in for a particular * session, even if the application does not reference it. */ @Override public void access() {    this.thisAccessedTime = System.currentTimeMillis();    if (ACTIVITY_CHECK) {     accessCount.incrementAndGet();   }  }

endAccess()

StandardSession.endAccess 方法是用来设置这个Session访问结束的时间的,何时被调用会在 Request 里讲。

/** * End the access. */ @Override public void endAccess() {    isNew = false;    /**   * The servlet spec mandates to ignore request handling time   * in lastAccessedTime.   */   if (LAST_ACCESS_AT_START) {     this.lastAccessedTime = this.thisAccessedTime;     this.thisAccessedTime = System.currentTimeMillis();   } else {     this.thisAccessedTime = System.currentTimeMillis();     this.lastAccessedTime = this.thisAccessedTime;   }    if (ACTIVITY_CHECK) {     accessCount.decrementAndGet();   }  }

isValid()

StandardSession.isValid 方法是很关键的,这个方法会用来判断这个Session是否还处于有效状态。

代码片段:

/** * Return the <code>isValid</code> flag for this session. */ @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) {     long timeNow = System.currentTimeMillis();     int timeIdle;     if (LAST_ACCESS_AT_START) {       timeIdle = (int) ((timeNow - lastAccessedTime) / 1000L);     } else {       timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);     }     if (timeIdle >= maxInactiveInterval) {       expire(true);     }   }    return this.isValid; }

ACTIVITY_CHECK ,的意思是判断session是否过期前,是否要先判断一下这个session是否还在使用中(用 accessCount 判断)

  • 如果是,那么这个session是不会过期的。

  • 如果不是,那么这个session就会被“粗暴”地过期。

LAST_ACCESS_AT_START ,是两种判断session过期方式的开关

  • 如果为true,会根据 getSession 的时间判断是否过期了。 access()endAccess() 之间的时间是不算进去的。

  • 如果为false,则根据session结束访问的时间判断是否过期了。 access()endAccess() 之间的时间是算进去的。

Request

doGetSession()

这个方法是tomcat获得session的地方,从下面的代码判断里可以看到,它会调用 StandardSession.access() 方法:

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) {       // 在这里调用了access       session.access();       return (session);     }   }   // ...   }

recycle()

这个当一个请求处理完毕后, CoyoteAdapter 会调用 Request.recycle() 方法,

而这个方法会调用 StandardSession.endAccess() 方法(也就是告诉Session,你的这次访问结束了)

/** * Release all object references, and initialize instance variables, in * preparation for reuse of this object. */ public void recycle() {    // ...     if (session != null) {     try {       session.endAccess();     } catch (Throwable t) {       ExceptionUtils.handleThrowable(t);       log.warn(sm.getString("coyoteRequest.sessionEndAccessFail"), t);     }   }   // ...  }

所以,当用户调用 HttpSession.getSession() 方法时,发生了这些事情:

  1. Request.doGetSession()

  2. StandardSession.access()

  3. 返回给用户Session

  4. 用户在Servlet里处理完请求

  5. Request.recycle()

  6. StandardSession.endAccess()

陷阱

从上面的流程可以看出Tomcat假设在Request的生命周期结束之后便不会有人再去访问Session了。

但是如果我们在处理Request的Thread A里另起一个Thread B,并且在Thread B里访问Session时会怎样呢?

你可能已经猜到,可能会访问到一个已经过期的Session。下面是一个小小的测试代码:

https://gist.github.com/chanjarster/e1793251477cbabfbe92

原文  https://segmentfault.com/a/1190000005982009
正文到此结束
Loading...