zoukankan      html  css  js  c++  java
  • Tomcat容器的Session管理

    Session管理是JavaEE容器比较重要的一部分,在app中也经常会用到。在开发app时,我们只是获取一个session,然后向session中存取数据,然后再销毁session。那么如何产生session,以及session池如何维护及管理,这些并没有在app涉及到。这些工作都是由容器来完成的。 
    Tomcat中主要由每个context容器内的一个Manager对象来管理session。对于这个manager对象的实现,可以根据tomcat提供的接口或基类来自己定制,同时,tomcat也提供了标准实现。 
    在tomcat架构分析(容器类)中已经介绍过,在每个context对象,即web app都具有一个独立的manager对象。通过server.xml可以配置定制化的manager,也可以不配置。不管怎样,在生成context对象时,都会生成一个manager对象。缺省的是StandardManager类,其类路径为: 

    引用
    org.apache.catalina.session.StandardManager


    Session对象也可以定制化实现,其主要实现标准servlet的session接口: 

    引用
    javax.servlet.http.HttpSession


    Tomcat也提供了标准的session实现: 

    引用
    org.apache.catalina.session.StandardSession


    本文主要就是结合消息流程介绍这两个类的实现,及session机制。 
    Session方面牵涉的东西还是蛮多的,例如HA,session复制是其中重要部分等,不过本篇主要从功能方面介绍session管理,有时间再说说扩展。 
    Session管理主要涉及到这几个方面: 

    • 创建session
    • 注销session
    • 持久化及启动加载session



    创建session 
    在具体说明session的创建过程之前,先看一下BS访问模型吧,这样理解直观一点。 

    1. browser发送Http request;
    2. tomcat内核Http11Processor会从HTTP request中解析出“jsessionid”(具体的解析过程为先从request的URL中解析,这是为了有的浏览器把cookie功能禁止后,将URL重写考虑的,如果解析不出来,再从cookie中解析相应的jsessionid),解析完后封装成一个request对象(当然还有其他的http header);
    3. servlet中获取session,其过程是根据刚才解析得到的jsessionid(如果有的话),从session池(session maps)中获取相应的session对象;这个地方有个逻辑,就是如果jsessionid为空的话(或者没有其对应的session对象,或者有session对象,但此对象已经过期超时),可以选择创建一个session,或者不创建;
    4. 如果创建新session,则将session放入session池中,同时将与其相对应的jsessionid写入cookie通过Http response header的方式发送给browser,然后重复第一步。


    以上是session的获取及创建过程。在servlet中获取session,通常是调用request的getSession方法。这个方法需要传入一个boolean参数,这个参数就是实现刚才说的,当jsessionid为空或从session池中获取不到相应的session对象时,选择创建一个新的session还是不创建。 
    看一下核心代码逻辑; 

    Java代码  收藏代码
    1. protected Session doGetSession(boolean create) {  
    2.   
    3.         ……  
    4.         // 先获取所在context的manager对象  
    5.         Manager manager = null;  
    6.         if (context != null)  
    7.             manager = context.getManager();  
    8.         if (manager == null)  
    9.             return (null);      // Sessions are not supported  
    10.           
    11.         //这个requestedSessionId就是从Http request中解析出来的  
    12.         if (requestedSessionId != null) {  
    13.             try {  
    14.                 //manager管理的session池中找相应的session对象  
    15.                 session = manager.findSession(requestedSessionId);  
    16.             } catch (IOException e) {  
    17.                 session = null;  
    18.             }  
    19.             //判断session是否为空及是否过期超时  
    20.             if ((session != null) && !session.isValid())  
    21.                 session = null;  
    22.             if (session != null) {  
    23.                 //session对象有效,记录此次访问时间  
    24.                 session.access();  
    25.                 return (session);  
    26.             }  
    27.         }  
    28.   
    29.         // 如果参数是false,则不创建新session对象了,直接退出了  
    30.         if (!create)  
    31.             return (null);  
    32.         if ((context != null) && (response != null) &&  
    33.             context.getCookies() &&  
    34.             response.getResponse().isCommitted()) {  
    35.             throw new IllegalStateException  
    36.               (sm.getString("coyoteRequest.sessionCreateCommitted"));  
    37.         }  
    38.   
    39.         // 开始创建新session对象  
    40.         if (connector.getEmptySessionPath()   
    41.                 && isRequestedSessionIdFromCookie()) {  
    42.             session = manager.createSession(getRequestedSessionId());  
    43.         } else {  
    44.             session = manager.createSession(null);  
    45.         }  
    46.   
    47.         // 将新session的jsessionid写入cookie,传给browser  
    48.         if ((session != null) && (getContext() != null)  
    49.                && getContext().getCookies()) {  
    50.             Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME,  
    51.                                        session.getIdInternal());  
    52.             configureSessionCookie(cookie);  
    53.             response.addCookieInternal(cookie);  
    54.         }  
    55.         //记录session最新访问时间  
    56.         if (session != null) {  
    57.             session.access();  
    58.             return (session);  
    59.         } else {  
    60.             return (null);  
    61.         }  
    62.     }  


    尽管不能贴出所有代码,但是上述的核心逻辑还是很清晰的。从中也可以看出,我们经常在servlet中这两种调用方式的不同; 
    新创建session 

    引用
    request.getSession(); 或者request.getSession(true);


    不创建session 

    引用
    request.getSession(false);


    接下来,看一下StandardManager的createSession方法,了解一下session的创建过程; 

    Java代码  收藏代码
    1. public Session createSession(String sessionId) {  
    2. 是个session数量控制逻辑,超过上限则抛异常退出  
    3.     if ((maxActiveSessions >= 0) &&  
    4.         (sessions.size() >= maxActiveSessions)) {  
    5.         rejectedSessions++;  
    6.         throw new IllegalStateException  
    7.             (sm.getString("standardManager.createSession.ise"));  
    8.     }  
    9.     return (super.createSession(sessionId));  
    10. }  


    这个最大支持session数量maxActiveSessions是可以配置的,先不管这个安全控制逻辑,看其主逻辑,即调用其基类的createSession方法; 

    Java代码  收藏代码
    1. public Session createSession(String sessionId) {  
    2.           
    3.         // 创建一个新的StandardSession对象  
    4.         Session session = createEmptySession();  
    5.   
    6.         // Initialize the properties of the new session and return it  
    7.         session.setNew(true);  
    8.         session.setValid(true);  
    9.         session.setCreationTime(System.currentTimeMillis());  
    10.         session.setMaxInactiveInterval(this.maxInactiveInterval);  
    11.         if (sessionId == null) {  
    12.             //设置jsessionid  
    13.             sessionId = generateSessionId();  
    14.         }  
    15.         session.setId(sessionId);  
    16.         sessionCounter++;  
    17.         return (session);  
    18.     }  


    关键是jsessionid的产生过程,接着看generateSessionId方法; 

    Java代码  收藏代码
    1. protected synchronized String generateSessionId() {  
    2.   
    3.         byte random[] = new byte[16];  
    4.         String jvmRoute = getJvmRoute();  
    5.         String result = null;  
    6.   
    7.         // Render the result as a String of hexadecimal digits  
    8.         StringBuffer buffer = new StringBuffer();  
    9.         do {  
    10.             int resultLenBytes = 0;  
    11.             if (result != null) {  
    12.                 buffer = new StringBuffer();  
    13.                 duplicates++;  
    14.             }  
    15.   
    16.             while (resultLenBytes < this.sessionIdLength) {  
    17.                 getRandomBytes(random);  
    18.                 random = getDigest().digest(random);  
    19.                 for (int j = 0;  
    20.                 j < random.length && resultLenBytes < this.sessionIdLength;  
    21.                 j++) {  
    22.                     byte b1 = (byte) ((random[j] & 0xf0) >> 4);  
    23.                     byte b2 = (byte) (random[j] & 0x0f);  
    24.                     if (b1 < 10)  
    25.                         buffer.append((char) ('0' + b1));  
    26.                     else  
    27.                         buffer.append((char) ('A' + (b1 - 10)));  
    28.                     if (b2 < 10)  
    29.                         buffer.append((char) ('0' + b2));  
    30.                     else  
    31.                         buffer.append((char) ('A' + (b2 - 10)));  
    32.                     resultLenBytes++;  
    33.                 }  
    34.             }  
    35.             if (jvmRoute != null) {  
    36.                 buffer.append('.').append(jvmRoute);  
    37.             }  
    38.             result = buffer.toString();  
    39.         //注意这个do…while结构  
    40.         } while (sessions.containsKey(result));  
    41.         return (result);  
    42.     }  


    这里主要说明的不是生成jsessionid的算法了,而是这个do…while结构。把这个逻辑抽象出来,可以看出; 
     
    如图所示,创建jsessionid的方式是由tomcat内置的加密算法算出一个随机的jsessionid,如果此jsessionid已经存在,则重新计算一个新的,直到确保现在计算的jsessionid唯一。 
    好了,至此一个session就这么创建了,像上面所说的,返回时是将jsessionid以HTTP response的header:“Set-cookie”发给客户端。 
    注销session 

    • 主动注销
    • 超时注销


    Session创建完之后,不会一直存在,或是主动注销,或是超时清除。即是出于安全考虑也是为了节省内存空间等。例如,常见场景:用户登出系统时,会主动触发注销操作。 
    主动注销 
    主动注销时,是调用标准的servlet接口: 

    引用
    session.invalidate();


    看一下tomcat提供的标准session实现(StandardSession) 

    Java代码  收藏代码
    1. public void invalidate() {  
    2.         if (!isValidInternal())  
    3.             throw new IllegalStateException  
    4.                 (sm.getString("standardSession.invalidate.ise"));  
    5.         // 明显的注销方法  
    6.         expire();  
    7.     }  


    Expire方法的逻辑稍后再说,先看看超时注销,因为它们调用的是同一个expire方法。 
    超时注销 
    Tomcat定义了一个最大空闲超时时间,也就是说当session没有被操作超过这个最大空闲时间时间时,再次操作这个session,这个session就会触发expire。 
    这个方法封装在StandardSession中的isValid()方法内,这个方法在获取这个request请求对应的session对象时调用,可以参看上面说的创建session环节。也就是说,获取session的逻辑是,先从manager控制的session池中获取对应jsessionid的session对象,如果获取到,就再判断是否超时,如果超时,就expire这个session了。 
    看一下tomcat提供的标准session实现(StandardSession) 

    Java代码  收藏代码
    1. public boolean isValid() {  
    2.         ……  
    3.         //这就是判断距离上次访问是否超时的过程  
    4.         if (maxInactiveInterval >= 0) {   
    5.             long timeNow = System.currentTimeMillis();  
    6.             int timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);  
    7.             if (timeIdle >= maxInactiveInterval) {  
    8.                 expire(true);  
    9.             }  
    10.         }  
    11.         return (this.isValid);  
    12.     }  


    Expire方法 
    是时候来看看expire方法了。 

    Java代码  收藏代码
    1. public void expire(boolean notify) {   
    2.   
    3.         synchronized (this) {  
    4.             ......  
    5.             //设立标志位  
    6.             setValid(false);  
    7.   
    8.             //计算一些统计值,例如此manager下所有session平均存活时间等  
    9.             long timeNow = System.currentTimeMillis();  
    10.             int timeAlive = (int) ((timeNow - creationTime)/1000);  
    11.             synchronized (manager) {  
    12.                 if (timeAlive > manager.getSessionMaxAliveTime()) {  
    13.                     manager.setSessionMaxAliveTime(timeAlive);  
    14.                 }  
    15.                 int numExpired = manager.getExpiredSessions();  
    16.                 numExpired++;  
    17.                 manager.setExpiredSessions(numExpired);  
    18.                 int average = manager.getSessionAverageAliveTime();  
    19.                 average = ((average * (numExpired-1)) + timeAlive)/numExpired;  
    20.                 manager.setSessionAverageAliveTime(average);  
    21.             }  
    22.   
    23.             // 将此session从manager对象的session池中删除  
    24.             manager.remove(this);  
    25.             ......  
    26.         }  
    27.     }  


    不需要解释,已经很清晰了。 
    这个超时时间是可以配置的,缺省在tomcat的全局web.xml下配置,也可在各个app下的web.xml自行定义; 

    Xml代码  收藏代码
    1. <session-config>  
    2.     <session-timeout>30</session-timeout>  
    3. </session-config>  


    单位是分钟。 
    Session持久化及启动初始化 
    这个功能主要是,当tomcat执行安全退出时(通过执行shutdown脚本),会将session持久化到本地文件,通常在tomcat的部署目录下有个session.ser文件。当启动tomcat时,会从这个文件读入session,并添加到manager的session池中去。 
    这样,当tomcat正常重启时, session没有丢失,对于用户而言,体会不到重启,不影响用户体验。 
    看一下概念图吧,觉得不是重要实现逻辑,代码就不说了。 
     
    总结 
    由此可以看出,session的管理是容器层做的事情,应用层一般不会参与session的管理,也就是说,如果在应用层获取到相应的session,已经是由tomcat提供的,因此如果过多的依赖session机制来进行一些操作,例如访问控制,安全登录等就不是十分的安全,因为如果有人能得到正在使用的jsessionid,则就可以侵入系统。

    http://gearever.iteye.com/blog/1546423

  • 相关阅读:
    异步加载技术实现瀑布流效果
    点击向下展开的下拉菜单特效
    几个个实用的PHP代码片段【自己备份】
    cache和buffer区别探讨
    windows 文本文件放到linux下使用
    制作rpm包
    mariadb在线热备份做主从
    检查目录下备份文件的脚本
    different between method and function
    mysql忘记root密码解决
  • 原文地址:https://www.cnblogs.com/doit8791/p/7594823.html
Copyright © 2011-2022 走看看