`

Session管理

 
阅读更多

Tomcat中的session管理,首先的明白一点 : 不是你客户端一个请求传过去,服务器就会为创建一个会话,

不是的,如果后台没有对应的session操作,就是你的servlet中没有session操作(getSession(boolean)),那么就不会给你创建会话;

 

在开发JEE应用中,通常我们都是对会话进行处理,而对于会话的创建,维护,删除,则是服务器替我们完成!

而在Tomcat中,与会话有关的两个重要的类 : StandardSession , StandardManager(这个manager就像一个组件一样,在tomcat启动的时候就会start) ;

而standardsession 则是实现了HttpSession ; session 只是一个简单的对象就像一个POJO,对session 进行的操作则是通过StandardManger 实现,它继承自

ManagerBase ;在ManagerBase中定义了成员变量:session池:

Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();

 

StandardSession中的属性:

  

/**
     * The collection of user data attributes associated with this Session.
     */
 protected Map attributes = new ConcurrentHashMap();

/**
     * The time this session was created, in milliseconds since midnight,
     * January 1, 1970 GMT.
     */
    protected long creationTime = 0L;

/**
     * The last accessed time for this Session.
     */
    protected volatile long lastAccessedTime = creationTime;


    /**
     * The session event listeners for this Session.
     */
    protected transient ArrayList listeners = new ArrayList();


    /**
     * The Manager with which this Session is associated.
     */
    protected transient Manager manager = null;

    * The maximum time interval, in seconds, between client requests before
     * the servlet container may invalidate this session.  A negative time
     * indicates that the session should never time out.
     */
    protected int maxInactiveInterval = -1;


    /**
     * Flag indicating whether this session is new or not.
     */
    protected boolean isNew = false;


    /**
     * Flag indicating whether this session is valid or not.
     */
    protected volatile boolean isValid = false;

    
    /**
     * Internal notes associated with this session by Catalina components
     * and event listeners.  <b>IMPLEMENTATION NOTE:</b> This object is
     * <em>not</em> saved and restored across session serializations!
     */
    protected transient Map notes = new Hashtable();


    /**
     * The authenticated Principal associated with this session, if any.
     * <b>IMPLEMENTATION NOTE:</b>  This object is <i>not</i> saved and
     * restored across session serializations!
     */
    protected transient Principal principal = null;

    /**
     * The access count for this session.
     */
    protected transient AtomicInteger accessCount = null;

 

 

session的创建过程 :

   调用getSession(boolean create)   // 取得会话对象,如果没有session, create = true时 ,则会创建一个,create=false , 返回null

  

public HttpSession getSession(boolean create) {
        Session session = doGetSession(create);
        if (session != null) {
            return session.getSession();
        } else {
            return null;
        }
    }

 

doGetSession(create) :

  

protected Session doGetSession(boolean create) {  
  
        ……  
        // 先获取所在context的manager对象  
        Manager manager = null;  
        if (context != null)  
            manager = context.getManager();  
        if (manager == null)  
            return (null);      // Sessions are not supported  
          
        //这个requestedSessionId就是从Http request中解析出来的  
        if (requestedSessionId != null) {  // 如果之前存在session
            try {  
                //manager管理的session池中找相应的session对象  
                session = manager.findSession(requestedSessionId);  
            } catch (IOException e) {  
                session = null;  
            }  
            //判断session是否为空及是否过期超时  
            if ((session != null) && !session.isValid())  
                session = null;  
            if (session != null) {  
                //session对象有效,记录此次访问时间  
                session.access();  
                return (session);  
            }  
        }  
  
        // 如果参数是false,则不创建新session对象了,直接退出了  
        if (!create)  
            return (null);  
        if ((context != null) && (response != null) &&  
            context.getCookies() &&  
            response.getResponse().isCommitted()) {  
            throw new IllegalStateException  
              (sm.getString("coyoteRequest.sessionCreateCommitted"));  
        }  
  
        // 开始创建新session对象  
        if (connector.getEmptySessionPath()   
                && isRequestedSessionIdFromCookie()) {  
            session = manager.createSession(getRequestedSessionId());  // 调用manager中方法创建session
        } else {  
            session = manager.createSession(null);  
        }  
  
        // 将新session的jsessionid写入cookie,传给browser  
        if ((session != null) && (getContext() != null)  
               && getContext().getCookies()) {  
            Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME,  
                                       session.getIdInternal());  
            configureSessionCookie(cookie);  
            response.addCookieInternal(cookie);  // response中的cookies就是在这里写入的
        }  
        //记录session最新访问时间  
        if (session != null) {  
            session.access();  
            return (session);  
        } else {  
            return (null);  
        }  
    }  

 接下来进入StandardManager的createSession方法:

  

public Session createSession(String sessionId) {  
//是个session数量控制逻辑,超过上限则抛异常退出  
    if ((maxActiveSessions >= 0) &&  
        (sessions.size() >= maxActiveSessions)) {  
        rejectedSessions++;  
        throw new IllegalStateException  
            (sm.getString("standardManager.createSession.ise"));  
    }  
    return (super.createSession(sessionId));  // 调用ManagerBase中的
}  

 

public Session createSession(String sessionId) {  
        // 创建一个新的StandardSession对象  
        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);  
        if (sessionId == null) {  
            //设置jsessionid  
            sessionId = generateSessionId();  
        }
// 就是这里顺便把session加入session池中  
        session.setId(sessionId);  
        sessionCounter++;  
        return (session);  
    }  

    setId() 方法 :把session对象加入session池中!

    中间省略set(String id) ;

public void setId(String id, boolean notify) {

        if ((this.id != null) && (manager != null))
            manager.remove(this);

        this.id = id;

        if (manager != null)
            manager.add(this); // 添加session到session池好隐蔽啊!!!
        
        if (notify) {
            tellNew();
        }
    }

 

   

generateSessionId() 生成sessionId:

   

protected synchronized String generateSessionId() {  
  
        byte random[] = new byte[16];  
        String jvmRoute = getJvmRoute();  
        String result = null;    
        // Render the result as a String of hexadecimal digits  
        StringBuffer buffer = new StringBuffer();  
        do {  
            int resultLenBytes = 0;  
            if (result != null) {  
                buffer = new StringBuffer();  
                duplicates++;  
            }    
            while (resultLenBytes < this.sessionIdLength) {  
                getRandomBytes(random);  
                random = getDigest().digest(random);  
                for (int j = 0;  
                j < random.length && resultLenBytes < this.sessionIdLength;  
                j++) {  
                    byte b1 = (byte) ((random[j] & 0xf0) >> 4);  
                    byte b2 = (byte) (random[j] & 0x0f);  
                    if (b1 < 10)  
                        buffer.append((char) ('0' + b1));  
                    else  
                        buffer.append((char) ('A' + (b1 - 10)));  
                    if (b2 < 10)  
                        buffer.append((char) ('0' + b2));  
                    else  
                        buffer.append((char) ('A' + (b2 - 10)));  
                    resultLenBytes++;  
                }  
            }  
            if (jvmRoute != null) {  
                buffer.append('.').append(jvmRoute);  
            }  
            result = buffer.toString();  
        //注意这个do…while结构  
        } while (sessions.containsKey(result));  // sessions就是那个session池,concurrentHashMap , 检查池中是否包含该key
        return (result);  
    }  

 到此 一个session对象就创建了!

 

   Session注销:

 

    主动注销

     调用sesion的servlet接口方法:

 

session.invalidate();

  // StanardSession中的invalidate()

public void invalidate() {  
        if (!isValidInternal())  
            throw new IllegalStateException  
                (sm.getString("standardSession.invalidate.ise"));  
        // 明显的注销方法  
        expire();  // expire 终止
    }  

 

     超时注销

     我们可以在web.xml中配置session的超时时间,也就是过了这个时间点,session就失效了,

     如果要你来设计,你会怎么做,每一个会话接入时,就会去扫描map一次,找到那些超时的,然后剔除吗! 

     这样,效率是不是降低了!

     那么,就是当你用这个会话的时候,再把它取出来,然后比较它是否过期,如果过期了,那么就重新创建一个会话!

     tomcat就是这么处理超时的!

     看源码 :

    

public boolean isValid() {  
        ……  
        //这就是判断距离上次访问是否超时的过程  
        if (maxInactiveInterval >= 0) {   
            long timeNow = System.currentTimeMillis();  
            int timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);  
            if (timeIdle >= maxInactiveInterval) {  
                expire(true);  
            }  
        }  
        return (this.isValid);  
    }  

   expire() 方法:

  

public void expire(boolean notify) {   
  
        synchronized (this) {  
            ......  
            //设立标志位  
            setValid(false);  
  
            //计算一些统计值,例如此manager下所有session平均存活时间等  
            long timeNow = System.currentTimeMillis();  
            int timeAlive = (int) ((timeNow - creationTime)/1000);  
            synchronized (manager) {  
                if (timeAlive > manager.getSessionMaxAliveTime()) {  
                    manager.setSessionMaxAliveTime(timeAlive);  
                }  
                int numExpired = manager.getExpiredSessions();  
                numExpired++;  
                manager.setExpiredSessions(numExpired);  
                int average = manager.getSessionAverageAliveTime();  
                average = ((average * (numExpired-1)) + timeAlive)/numExpired;  
                manager.setSessionAverageAliveTime(average);  
            }  
  
            // 将此session从manager对象的session池中删除  
            manager.remove(this);  
            ......  
        }  
    }  

 

Session的持久化 和 启动初始化:

   tomcat执行安全退出时(通过执行shutdown脚本),会将session持久化到本地文件,通常在tomcat的部署目录下有个session.ser文件。当启动tomcat时,会从这个文件读入session,并添加到manager的session池中    去。 这样,当tomcat正常重启时, session没有丢失,对于用户而言,体会不到重启,不影响用户体验。 

   可以把会话信息写入文件,也可以存入数据库中!

 

  

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics