1. 程式人生 > >SpringBoot中Session超時原理說明

SpringBoot中Session超時原理說明

一:前言:
最近支付後臺登入一段時間後如果沒有任何操作,總是需要重新登入才可以繼續訪問頁面,出現這個問題的原因就是session超時,debug程式碼後發現session的超時時間是1800s。也就是說當1800秒內沒有任何操作,session就會出現超時現象。那這個超時時間是如何設定的呢?然後該如何重新設定此超時時間呢?系統又如何判斷session超時的呢?接下來就一一進行解答。

二:系統session超時時間如何預設的?
說明:獲取session超時時間的方法為”request.getSession().getMaxInactiveInterval()",但是tomcat中設定超時時間的引數為“sessionTimeout”,那麼他們是怎麼聯絡起來的呢?

第一步:載入sessionTimeout引數。
  1、專案執行初始化通過“@ConfigurationProperties”註解載入“org.springframework.boot.autoconfigure.web.ServerProperties”類。

//springBoot中預設的配置檔案為"application.yml"或者"application.perties"檔案,也就是說server是其中的一個配置引數。

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties
      implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
//程式碼
}

2、上面類中“ServerProperties”繼承自“EmbeddedServletContainerCustomizer”介面。重寫customize方法,之後在此方法中“向上推”,即可找到“AbstractConfigurableEmbeddedServletContainer  ”類。

@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
   //多個引數判斷,如果在application中沒配置的情況下都是null
  if (getPort() != null) {
      container.setPort(getPort());
   }
  ...//n多個引數判斷,
    //以下的程式碼就是重點,因為是tomcat容器,所以以下條件為“真”,經過一系列的查詢父類或者實現介面即可找到抽象類“AbstractConfigurableEmbeddedServletContainer”
    //public class TomcatEmbeddedServletContainerFactory extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware
    //public abstract class AbstractEmbeddedServletContainerFactory  extends AbstractConfigurableEmbeddedServletContainer  implements EmbeddedServletContainerFactory 
   if (container instanceof TomcatEmbeddedServletContainerFactory) {
      getTomcat().customizeTomcat(this,
            (TomcatEmbeddedServletContainerFactory) container);
   }


//以上程式碼執行完成之後,實際上已經有對應的session所有的預設引數,之後通過下面方法,將所有引數放入對應的容器中。第3、4步就是設定過程
  container.addInitializers(new SessionConfiguringInitializer(this.session));
}

3、在“AbstractConfigurableEmbeddedServletContainer”類中終於可以找到“超時時間”的相關設定

//重要程式碼
//45行
private static final int DEFAULT_SESSION_TIMEOUT = (int) TimeUnit.MINUTES
      .toSeconds(30);
//66行
private int sessionTimeout = DEFAULT_SESSION_TIMEOUT;
 
@Override
public void setSessionTimeout(int sessionTimeout) {
   this.sessionTimeout = sessionTimeout;
}
//171-188行
@Override
public void setSessionTimeout(int sessionTimeout, TimeUnit timeUnit) {
   Assert.notNull(timeUnit, "TimeUnit must not be null");
   this.sessionTimeout = (int) timeUnit.toSeconds(sessionTimeout);
}

/**
 * Return the session timeout in seconds.
 * @return the timeout in seconds
 */
public int getSessionTimeout() {
   return this.sessionTimeout;
}

4、執行第2步的”container.addInitializers(new SessionConfiguringInitializer(this.session))“載入所有的配置引數。

public static class Session {

   /**
    * Session timeout in seconds.
    */
   private Integer timeout;

   public Integer getTimeout() {
      return this.timeout;
   }
//將session超時時間設定進來
   public void setTimeout(Integer sessionTimeout) {
      this.timeout = sessionTimeout;
   }

第二步:將上面的超時時間賦值給“MaxInactiveInterval”引數。
說明:既然上面tomcat需要的引數都已經載入完成,那麼接下來就會執行tomcat,此處不做細講,直接進入tomcat啟動和載入引數說明。在“TomcatEmbeddedServletContainerFactory”類中的方法呼叫流程如下:

getEmbeddedServletContainer--》prepareContext--》configureContext--》configureSession--》getSessionTimeoutInMinutes。

1、呼叫configureSession設定tomcat的Session配置引數。

//以下程式碼
private void configureSession(Context context) {
   long sessionTimeout = getSessionTimeoutInMinutes();
   context.setSessionTimeout((int) sessionTimeout);
   Manager manager = context.getManager();
   if (manager == null) {
      manager = new StandardManager();
      //此處即為設定相應的引數的位置。之後會呼叫StandardContext類的setManger(Manager)方法,在setManger中會呼叫"manager.setContext(this)"
      context.setManager(manager);
   }
}


//計算超時時間為分鐘(注意:此處會將之前的1800秒,轉換為30分鐘)。可以看出最終的時間結果是個整數的分鐘型別,也就是說如果設定的超時時間(單位為秒)不是60的倍數,也會最終轉換為60的倍數,並且最小超時時間設定的是60秒。

private long getSessionTimeoutInMinutes() {
   long sessionTimeout = getSessionTimeout();
   if (sessionTimeout > 0) {
      sessionTimeout = Math.max(TimeUnit.SECONDS.toMinutes(sessionTimeout), 1L);
   }
   return sessionTimeout;
}


2、最終將SessionTimeout賦值給MaxInactiveInterval。終於完成session超時時間設定。

//以下程式碼
@Override
public void setContext(Context context) {
    //省略其餘設定程式碼,直接重新設定Session超時時間,此時又將上面的分鐘單位轉為秒。此時終於給Sesseion設定了預設超時時間。
    if (this.context != null) {
        setMaxInactiveInterval(this.context.getSessionTimeout() * 60);
        this.context.addPropertyChangeListener(this);
    }
}


三:如果自定義超時時間呢?
其實從上面的流程,已經不難看出,只需要在“org.springframework.boot.autoconfigure.web.ServerProperties”類中找到對應的Session引數,初始化讓其載入上來即可完成設定。

/**
 * Get the session timeout.
 * @return the session timeout
 * @deprecated since 1.3.0 in favor of {@code session.timeout}.
 */
@Deprecated
@DeprecatedConfigurationProperty(replacement = "server.session.timeout")
public Integer getSessionTimeout() {
   return this.session.getTimeout();
}

所以在application中配置“server.session.timeout“即可,引數型別為long型別,單位為”秒“。

四:執行程式是如何判斷session超時的?
其實很簡單:只需要在每次本次同一個sessionequest請求的時間,和之前的請求時間進行比較,發現兩個值的差已經大於MaxInactiveInterval的值即可。

//判斷是否超時
@Override
public boolean isValid() {
    //省略多個條件判斷
    if (maxInactiveInterval > 0) {
       //判斷此session空閒時間是否比maxInactiveInterval大,如果大的情況下,session就超時
        int timeIdle = (int) (getIdleTimeInternal() / 1000L);
        if (timeIdle >= maxInactiveInterval) {
            expire(true);
        }
    }
    return this.isValid;
}
//將上次訪問時間和當前時間比較,拿到空閒時間值
@Override
public long getIdleTimeInternal() {
    long timeNow = System.currentTimeMillis();
    long timeIdle;
    if (LAST_ACCESS_AT_START) {
        timeIdle = timeNow - lastAccessedTime;
    } else {
        timeIdle = timeNow - thisAccessedTime;
    }
    return timeIdle;
}


說明:

所以為了保證session超時時間長點,可以在application配置檔案中配置“server.session.timeout”引數即可,引數單位為“秒”,如果引數不是60的整數倍,會轉換成60的整數倍(見二:系統如何設定超時時間、步驟二中的“1”中演算法)。如不滿一分鐘,會轉換為60秒。

擴充套件:

實際上也可以直接重寫EmbeddedServletContainerCustomizer的customize方法進行賦值。

 @Bean
 public EmbeddedServletContainerCustomizer containerCustomizer(){
        return new EmbeddedServletContainerCustomizer() {
            @Override
            public void customize(ConfigurableEmbeddedServletContainer container) {
                 container.setSessionTimeout(600);//單位為S
           }
     };
 }