前面几篇我们分析了Tomcat的启动,关闭,请求处理的流程,tomcat的classloader机制,本篇将接着分析Tomcat的session管理方面的内容。

在开始之前,我们先来看一下总体上的结构,熟悉了总体结构以后,我们在一步步的去分析源代码。Tomcat session相光的类图如下:

通过上图,我们可以看出每一个StandardContext会关联一个Manager,默认情况下Manager的实现类是StandardManager,而StandardManager内部会聚合多个Session,其中StandardSession是Session的默认实现类,当我们调用Request.getSession的时候,Tomcat通过StandardSessionFacade这个外观类将StandardSession包装以后返回。

上面清楚了总体的结构以后,我们来进一步的通过源代码来分析一下。咋们首先从Request的getSession方法看起。

[java] view plaincopy
  1. org.apache.catalina.connector.Request#getSession
[java] view plaincopy
  1. public HttpSession getSession() {
  2. Session session = doGetSession(true);
  3. if (session == null) {
  4. return null;
  5. }
  6. return session.getSession();
  7. }

从上面的代码,我们可以看出首先首先调用doGetSession方法获取Session,然后再调用Session的getSession方法返回HttpSession,那接下来我们再来看看doGetSession方法:

[java] view plaincopy
  1. org.apache.catalina.connector.Request#doGetSession
  2. protected Session doGetSession(boolean create) {
  3. // There cannot be a session if no context has been assigned yet
  4. if (context == null) {
  5. return (null);
  6. }
  7. // Return the current session if it exists and is valid
  8. if ((session != null) && !session.isValid()) {
  9. session = null;
  10. }
  11. if (session != null) {
  12. return (session);
  13. }
  14. // Return the requested session if it exists and is valid
  15. // 1
  16. Manager manager = null;
  17. if (context != null) {
  18. manager = context.getManager();
  19. }
  20. if (manager == null)
  21. {
  22. return (null);      // Sessions are not supported
  23. }
  24. // 2
  25. if (requestedSessionId != null) {
  26. try {
  27. session = manager.findSession(requestedSessionId);
  28. } catch (IOException e) {
  29. session = null;
  30. }
  31. if ((session != null) && !session.isValid()) {
  32. session = null;
  33. }
  34. if (session != null) {
  35. session.access();
  36. return (session);
  37. }
  38. }
  39. // Create a new session if requested and the response is not committed
  40. // 3
  41. if (!create) {
  42. return (null);
  43. }
  44. if ((context != null) && (response != null) &&
  45. context.getServletContext().getEffectiveSessionTrackingModes().
  46. contains(SessionTrackingMode.COOKIE) &&
  47. response.getResponse().isCommitted()) {
  48. throw new IllegalStateException
  49. (sm.getString("coyoteRequest.sessionCreateCommitted"));
  50. }
  51. // Attempt to reuse session id if one was submitted in a cookie
  52. // Do not reuse the session id if it is from a URL, to prevent possible
  53. // phishing attacks
  54. // Use the SSL session ID if one is present.
  55. // 4
  56. if (("/".equals(context.getSessionCookiePath())
  57. && isRequestedSessionIdFromCookie()) || requestedSessionSSL ) {
  58. session = manager.createSession(getRequestedSessionId());
  59. } else {
  60. session = manager.createSession(null);
  61. }
  62. // Creating a new session cookie based on that session
  63. if ((session != null) && (getContext() != null)
  64. && getContext().getServletContext().
  65. getEffectiveSessionTrackingModes().contains(
  66. SessionTrackingMode.COOKIE)) {
  67. // 5
  68. Cookie cookie =
  69. ApplicationSessionCookieConfig.createSessionCookie(
  70. context, session.getIdInternal(), isSecure());
  71. response.addSessionCookieInternal(cookie);
  72. }
  73. if (session == null) {
  74. return null;
  75. }
  76. session.access();
  77. return session;
  78. }

下面我们就来重点分析一下,上面代码中标注了数字的地方:

  1. 标注1(第17行)首先从StandardContext中获取对应的Manager对象,缺省情况下,这个地方获取的其实就是StandardManager的实例。
  2. 标注2(第26行)从Manager中根据requestedSessionId获取session,如果session已经失效了,则将session置为null以便下面创建新的session,如果session不为空则通过调用session的access方法标注session的访问时间,然后返回。
  3. 标注3(第43行)判断传递的参数,如果为false,则直接返回空,这其实就是对应的Request.getSession(true/false)的情况,当传递false的时候,如果不存在session,则直接返回空,不会新建。
  4. 标注4 (第59行)调用Manager来创建一个新的session,这里默认会调用到StandardManager的方法,而StandardManager继承了ManagerBase,那么默认其实是调用了了ManagerBase的方法。
  5. 标注5 (第72行)创建了一个Cookie,而Cookie的名称就是大家熟悉的JSESSIONID,另外JSESSIONID其实也是可以配置的,这个可以通过context节点的sessionCookieName来修改。比如….

通过doGetSession获取到Session了以后,我们发现调用了session.getSession方法,而Session的实现类是StandardSession,那么我们再来看下StandardSession的getSession方法。

[java] view plaincopy
  1. org.apache.catalina.session.StandardSession#getSession
  2. public HttpSession getSession() {
  3. if (facade == null){
  4. if (SecurityUtil.isPackageProtectionEnabled()){
  5. final StandardSession fsession = this;
  6. facade = AccessController.doPrivileged(
  7. new PrivilegedAction<StandardSessionFacade>(){
  8. @Override
  9. public StandardSessionFacade run(){
  10. return new StandardSessionFacade(fsession);
  11. }
  12. });
  13. } else {
  14. facade = new StandardSessionFacade(this);
  15. }
  16. }
  17. return (facade);
  18. }

通过上面的代码,我们可以看到通过StandardSessionFacade的包装类将StandardSession包装以后返回。到这里我想大家应该熟悉了Session创建的整个流程。

接着我们再来看看,Sesssion是如何被销毁的。我们在Tomcat启动过程(Tomcat源代码阅读系列之三)中之处,在容器启动以后会启动一个ContainerBackgroundProcessor线程,这个线程是在Container启动的时候启动的,这条线程就通过后台周期性的调用org.apache.catalina.core.ContainerBase#backgroundProcess,而backgroundProcess方法最终又会调用org.apache.catalina.session.ManagerBase#backgroundProcess,接下来我们就来看看Manger的backgroundProcess方法。

[java] view plaincopy
  1. org.apache.catalina.session.ManagerBase#backgroundProcess
  2. public void backgroundProcess() {
  3. count = (count + 1) % processExpiresFrequency;
  4. if (count == 0)
  5. processExpires();
  6. }

上面的代码里,需要注意一下,默认情况下backgroundProcess是每10秒运行一次(StandardEngine构造的时候,将backgroundProcessorDelay设置为了10),而这里我们通过processExpiresFrequency来控制频率,例如processExpiresFrequency的值默认为6,那么相当于没一分钟运行一次processExpires方法。接下来我们再来看看processExpires。

[java] view plaincopy
  1. org.apache.catalina.session.ManagerBase#processExpires
  2. public void processExpires() {
  3. long timeNow = System.currentTimeMillis();
  4. Session sessions[] = findSessions();
  5. int expireHere = 0 ;
  6. if(log.isDebugEnabled())
  7. log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
  8. for (int i = 0; i < sessions.length; i++) {
  9. if (sessions[i]!=null && !sessions[i].isValid()) {
  10. expireHere++;
  11. }
  12. }
  13. long timeEnd = System.currentTimeMillis();
  14. if(log.isDebugEnabled())
  15. log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere);
  16. processingTime += ( timeEnd - timeNow );
  17. }

上面的代码比较简单,首先查找出当前context的所有的session,然后调用session的isValid方法,接下来我们在看看Session的isValid方法。

[java] view plaincopy
  1. org.apache.catalina.session.StandardSession#isValid
  2. public boolean isValid() {
  3. if (this.expiring) {
  4. return true;
  5. }
  6. if (!this.isValid) {
  7. return false;
  8. }
  9. if (ACTIVITY_CHECK && accessCount.get() > 0) {
  10. return true;
  11. }
  12. if (maxInactiveInterval > 0) {
  13. long timeNow = System.currentTimeMillis();
  14. int timeIdle;
  15. if (LAST_ACCESS_AT_START) {
  16. timeIdle = (int) ((timeNow - lastAccessedTime) / 1000L);
  17. } else {
  18. timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);
  19. }
  20. if (timeIdle >= maxInactiveInterval) {
  21. expire(true);
  22. }
  23. }
  24. return (this.isValid);
  25. }

查看上面的代码,主要就是通过对比当前时间和上次访问的时间差是否大于了最大的非活动时间间隔,如果大于就会调用expire(true)方法对session进行超期处理。这里需要注意一点,默认情况下LAST_ACCESS_AT_START为false,读者也可以通过设置系统属性的方式进行修改,而如果采用LAST_ACCESS_AT_START的时候,那么请求本身的处理时间将不算在内。比如一个请求处理开始的时候是10:00,请求处理花了1分钟,那么如果LAST_ACCESS_AT_START为true,则算是否超期的时候,是从10:00算起,而不是10:01。

接下来我们再来看看expire方法,代码如下:

[java] view plaincopy
  1. org.apache.catalina.session.StandardSession#expire
  2. public void expire(boolean notify) {
  3. // Check to see if expire is in progress or has previously been called
  4. if (expiring || !isValid)
  5. return;
  6. synchronized (this) {
  7. // Check again, now we are inside the sync so this code only runs once
  8. // Double check locking - expiring and isValid need to be volatile
  9. if (expiring || !isValid)
  10. return;
  11. if (manager == null)
  12. return;
  13. // Mark this session as "being expired"
  14. // 1
  15. expiring = true;
  16. // Notify interested application event listeners
  17. // FIXME - Assumes we call listeners in reverse order
  18. Context context = (Context) manager.getContainer();
  19. // The call to expire() may not have been triggered by the webapp.
  20. // Make sure the webapp's class loader is set when calling the
  21. // listeners
  22. ClassLoader oldTccl = null;
  23. if (context.getLoader() != null &&
  24. context.getLoader().getClassLoader() != null) {
  25. oldTccl = Thread.currentThread().getContextClassLoader();
  26. if (Globals.IS_SECURITY_ENABLED) {
  27. PrivilegedAction<Void> pa = new PrivilegedSetTccl(
  28. context.getLoader().getClassLoader());
  29. AccessController.doPrivileged(pa);
  30. } else {
  31. Thread.currentThread().setContextClassLoader(
  32. context.getLoader().getClassLoader());
  33. }
  34. }
  35. try {
  36. // 2
  37. Object listeners[] = context.getApplicationLifecycleListeners();
  38. if (notify && (listeners != null)) {
  39. HttpSessionEvent event =
  40. new HttpSessionEvent(getSession());
  41. for (int i = 0; i < listeners.length; i++) {
  42. int j = (listeners.length - 1) - i;
  43. if (!(listeners[j] instanceof HttpSessionListener))
  44. continue;
  45. HttpSessionListener listener =
  46. (HttpSessionListener) listeners[j];
  47. try {
  48. context.fireContainerEvent("beforeSessionDestroyed",
  49. listener);
  50. listener.sessionDestroyed(event);
  51. context.fireContainerEvent("afterSessionDestroyed",
  52. listener);
  53. } catch (Throwable t) {
  54. ExceptionUtils.handleThrowable(t);
  55. try {
  56. context.fireContainerEvent(
  57. "afterSessionDestroyed", listener);
  58. } catch (Exception e) {
  59. // Ignore
  60. }
  61. manager.getContainer().getLogger().error
  62. (sm.getString("standardSession.sessionEvent"), t);
  63. }
  64. }
  65. }
  66. } finally {
  67. if (oldTccl != null) {
  68. if (Globals.IS_SECURITY_ENABLED) {
  69. PrivilegedAction<Void> pa =
  70. new PrivilegedSetTccl(oldTccl);
  71. AccessController.doPrivileged(pa);
  72. } else {
  73. Thread.currentThread().setContextClassLoader(oldTccl);
  74. }
  75. }
  76. }
  77. if (ACTIVITY_CHECK) {
  78. accessCount.set(0);
  79. }
  80. setValid(false);
  81. // Remove this session from our manager's active sessions
  82. // 3
  83. manager.remove(this, true);
  84. // Notify interested session event listeners
  85. if (notify) {
  86. fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);
  87. }
  88. // Call the logout method
  89. if (principal instanceof GenericPrincipal) {
  90. GenericPrincipal gp = (GenericPrincipal) principal;
  91. try {
  92. gp.logout();
  93. } catch (Exception e) {
  94. manager.getContainer().getLogger().error(
  95. sm.getString("standardSession.logoutfail"),
  96. e);
  97. }
  98. }
  99. // We have completed expire of this session
  100. expiring = false;
  101. // Unbind any objects associated with this session
  102. // 4
  103. String keys[] = keys();
  104. for (int i = 0; i < keys.length; i++)
  105. removeAttributeInternal(keys[i], notify);
  106. }
  107. }

上面代码的主流程我已经标注了数字,我们来逐一分析一下:

  1. 标注1(第18行)标记当前的session为超期
  2. 标注2(第41行)出发HttpSessionListener监听器的方法。
  3. 标注3(第89行)从Manager里面移除当前的session
  4. 标注4(第113行)将session中保存的属性移除。

到这里我们已经清楚了Tomcat中对与StandardSession的创建以及销毁的过程,其实StandardSession仅仅是实现了内存中Session的存储,而Tomcat还支持将Session持久化,以及Session集群节点间的同步。这些内容我们以后再来分析。

Tomcat源码解析七:Tomcat Session管理机制相关推荐

  1. 【kafka】Kafka 源码解析:Group 协调管理机制

    1.概述 转载:Kafka 源码解析:Group 协调管理机制 在 Kafka 的设计中,消费者一般都有一个 group 的概念(当然,也存在不属于任何 group 的消费者),将多个消费者组织成一个 ...

  2. Tomcat源码解析系列二:Tomcat总体架构

    Tomcat即是一个HTTP服务器,也是一个servlet容器,主要目的就是包装servlet,并对请求响应相应的servlet,纯servlet的web应用似乎很好理解Tomcat是如何装载serv ...

  3. 源码解析 使用tomcat作为web容器时,用到的外观模式

    源码解析 使用tomcat作为web容器时,接收浏览器发送过来的请求, tomcat会将请求信息封装成ServletRequest对象, 如下图①处对象. 但是大家想想ServletRequest是一 ...

  4. Tomcat源码解析五:Tomcat请求处理过程

    前面已经分析完了Tomcat的启动和关闭过程,本篇就来接着分析一下Tomcat中请求的处理过程. 在开始本文之前,咋们首先来看看一个Http请求处理的过程,一般情况下是浏览器发送http请求-> ...

  5. Tomcat 源码解析一初识

      为什么我想研究Tomcat源码,我们现在都用的是SpringBoot开发项目,而SpringBoot对错Tomcat集成,导致现在基本上看不到Tomcat的身影了,但是Tomcat不存在吗?只要我 ...

  6. Tomcat源码解析一:下载源码与导入eclipse

    自从写web程序以来,web程序是如何在Tomcat中运行的一直困惑着我,不知道底层的运行机制是无法真正理解web的,所以就开始研究Tomcat源码,Tomcat是一个轻量级的java服务器,再结合& ...

  7. tomcat源码分析_CVE-2020-9484 tomcat session反序列化漏洞分析

    作者:N1gh5合天智汇 title: CVE-2020-9484 tomcat session反序列化漏洞分析 tags: CVE,Tomcat,反序列化 grammar_cjkRuby: true ...

  8. Tomcat源码解析:环境搭建

    下载源码 从github下载tomcat源码 git clone https://github.com/apache/tomcat.git 或者直接fork一份到自己仓库,以便后续添加注释,附上笔者自 ...

  9. ⭐openGauss数据库源码解析系列文章—— 角色管理⭐

    在前面介绍过"9.1 安全管理整体架构和代码概览.9.2 安全认证",本篇我们介绍第9章 安全管理源码解析中"9.3 角色管理"的相关精彩内容介绍. 9.3 角 ...

最新文章

  1. Mysql连接数据库的操作
  2. flask 文件上传
  3. php与服务器关系,php与web服务器关系
  4. C语言/找出任意两整数之间的素数以及他们的和
  5. Python3爬虫反反爬之搞定同程旅游加密参数 antitoken
  6. Java SE 8 docs:Static Methods、Instance Methods、Abstract Methods、Concrete Methods和Fields
  7. 3d max用不同目标做关键帧_3D动画制作流程大解析
  8. mysql执行语句返回主键_mysql语句insert后返回主键
  9. LintCode 137. 克隆图
  10. storm配置:如何解决worker进程内存过小的问题
  11. 什么是银行的表内表外业务?
  12. AR涂涂乐项目之识别图制作模型的制作一
  13. 实现线程的三种方式KLT/ULT/LWP
  14. 名帖69 颜真卿 楷书《自书告身帖》
  15. 为ibus输入法框架制作新世纪五笔码表
  16. 达梦数据库表新增字段速度测试
  17. UA OPTI544 量子光学1 Maxwell方程与Lorentz Oscillator回顾
  18. Tuscany SCA软件架构设计理念分析鉴赏 (一)
  19. 从精英云到普惠云,青云QingCloud的“性格”变了
  20. 【SystemC】(二)第一个SystemC程序

热门文章

  1. pod资源限制,探针,指定资源
  2. Mysql数据库函数(数字,字符串,日期时间)
  3. Linux sed编辑器
  4. php教程链接,php自动给网址加上链接的方法,php网址链接方法_PHP教程
  5. aoe网最早开始时间和最迟开始时间_关键路径(AOE)网 通俗易懂
  6. java array iterator_java数组遍历——iterator和for方法
  7. visual studio 设计器不显示_面向国际市场的装置开发运维软件设计与实现
  8. android studio 加载ffmpeg.so,Android studio使用已经编译好的ffmpeg .so库
  9. linux开权限变绿了,解读Linux文件权限的设置方法
  10. c语言判断一个点在长方体内部_21个入门练手项目,让你轻松玩转C语言