首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 开源软件 >

Tomcat的Session治理与后台处理

2012-08-13 
Tomcat的Session管理与后台处理public HttpSession getSession(boolean create) {if (request null) {t

Tomcat的Session管理与后台处理
public HttpSession getSession(boolean create) { if (request == null) { throw new IllegalStateException( sm.getString("requestFacade.nullRequest")); } if (SecurityUtil.isPackageProtectionEnabled()){ return (HttpSession)AccessController. doPrivileged(new GetSessionPrivilegedAction(create)); } else { return request.getSession(create); } }

    public HttpSession getSession() {        if (request == null) {            throw new IllegalStateException(                            sm.getString("requestFacade.nullRequest"));        }        return getSession(true);    }
其实差不太多,最后都会进入org.apache.catalina.connector.Request的#getSession()方法。这个方法的源代码如下:
    public HttpSession getSession(boolean create) {        Session session = doGetSession(create);        if (session != null) {            return session.getSession();        } else {            return null;        }    }
然后调用就到了#doGetSession()这个方法了。源代码如下
protected Session doGetSession(boolean create) {// 没有Context的话直接返回nullif (context == null)return (null);// 判断Session是否有效if ((session != null) && !session.isValid())session = null;if (session != null)return (session);// 返回Manager对象,这里是StandardManager类的对象Manager manager = null;if (context != null)manager = context.getManager();if (manager == null)return (null); // Sessions are not supported// 判断是否有SessionIDif (requestedSessionId != null) {try {// 在Manager中根据SessionID查找Sessionsession = manager.findSession(requestedSessionId);} catch (IOException e) {session = null;}if ((session != null) && !session.isValid())session = null;if (session != null) {// 更新访问时间session.access();return (session);}}// 创建新的Sessionif (!create)return (null);if ((context != null) && (response != null) && context.getCookies()&& response.getResponse().isCommitted()) {throw new IllegalStateException(sm.getString("coyoteRequest.sessionCreateCommitted"));}// 判断是否使用 "/" 作为Session Cookie的存储路径 并且 是否SessionID来自Cookieif (connector.getEmptySessionPath() && isRequestedSessionIdFromCookie()) {// 创建Sessionsession = manager.createSession(getRequestedSessionId());} else {session = manager.createSession(null);}// 创建一个新的Session Cookiesif ((session != null) && (getContext() != null) && getContext().getCookies()) {Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME, session.getIdInternal());// 配置Session CookieconfigureSessionCookie(cookie);// 在响应中加入Session Cookieresponse.addCookieInternal(cookie);}if (session != null) {// 更新访问时间session.access();return (session);} else {return (null);}}
?这个方法说明了Session创建的大致过程,首先判断requestedSessionId是否存在,如果存在,那么根据这个ID去查找Session对象。如果requestedSessionId不存在或者没有取到Session,并且传递给#getSession(boolean)的参数为真,那么要创建一个新的Session,并且给客户端写回去一个Session Cookie。

首先,我感兴趣的是requestedSessionId的赋值,它到底是什么时候被赋值的呢?

还要向回看Tomcat的请求处理过程,请求曾到过这一步,org.apache.catalina.connector.CoyoteAdapter的#service()方法。里边有这样一句方法调用:postParseRequest(req, request, res, response)。就是这一步处理了SessionID的获取,这个方法调用了#parseSessionId()和parseSessionCookiesId()这两个方法,就是它对Session ID进行了提取,源代码分别如下:
protected void parseSessionId(org.apache.coyote.Request req, Request request) {ByteChunk uriBC = req.requestURI().getByteChunk();// 判断URL中是不是有";jsessionid="这个字符串int semicolon = uriBC.indexOf(match, 0, match.length(), 0);if (semicolon > 0) {// Parse session ID, and extract it from the decoded request URI// 在URL中提取Session IDint start = uriBC.getStart();int end = uriBC.getEnd();int sessionIdStart = semicolon + match.length();int semicolon2 = uriBC.indexOf(';', sessionIdStart);if (semicolon2 >= 0) {request.setRequestedSessionId(new String(uriBC.getBuffer(), start + sessionIdStart,semicolon2 - sessionIdStart));byte[] buf = uriBC.getBuffer();for (int i = 0; i < end - start - semicolon2; i++) {buf[start + semicolon + i] = buf[start + i + semicolon2];}uriBC.setBytes(buf, start, end - start - semicolon2 + semicolon);} else {request.setRequestedSessionId(new String(uriBC.getBuffer(), start + sessionIdStart,(end - start) - sessionIdStart));uriBC.setEnd(start + semicolon);}// 设定Session ID来自于URLrequest.setRequestedSessionURL(true);} else {request.setRequestedSessionId(null);request.setRequestedSessionURL(false);}}

protected void parseSessionCookiesId(org.apache.coyote.Request req, Request request) {Context context = (Context) request.getMappingData().context;if (context != null && !context.getCookies())return;// 返回CookieCookies serverCookies = req.getCookies();int count = serverCookies.getCookieCount();if (count <= 0)return;for (int i = 0; i < count; i++) {ServerCookie scookie = serverCookies.getCookie(i);// 判断是否有JSESSIONID这个名字的Cookieif (scookie.getName().equals(Globals.SESSION_COOKIE_NAME)) {// Override anything requested in the URLif (!request.isRequestedSessionIdFromCookie()) {// 设定Session IDconvertMB(scookie.getValue());request.setRequestedSessionId(scookie.getValue().toString());// 如果之前在URL中读到了SessionID,那么会覆盖它request.setRequestedSessionCookie(true);request.setRequestedSessionURL(false);if (log.isDebugEnabled())log.debug(" Requested cookie session id is " + request.getRequestedSessionId());} else {if (!request.isRequestedSessionIdValid()) {convertMB(scookie.getValue());request.setRequestedSessionId(scookie.getValue().toString());}}}}}
?Tomcat就是通过上边的两个方法读到URL或者Cookie中存放的Session ID的。

了解了Session ID的获取,下面要看一下Session的查找过程,就是org.apache.catalina.session.StandardManager的#findSession()方法。这个方法是在它的基类中定义的,源代码如下:
    public Session findSession(String id) throws IOException {        if (id == null)            return (null);        return (Session) sessions.get(id);    }
?代码很短,其中sessions是一个ConcurrentHashMap<String, Session>对象。那么这个sessions的对象是什么时候载入的Session呢?

启动的时候!可以看一下StandardManager#start()方法。最后调用了#load()方法,这个就是载入Session的方法了:
public void load() throws ClassNotFoundException, IOException {if (SecurityUtil.isPackageProtectionEnabled()) {try {AccessController.doPrivileged(new PrivilegedDoLoad());} catch (PrivilegedActionException ex) {Exception exception = ex.getException();if (exception instanceof ClassNotFoundException) {throw (ClassNotFoundException) exception;} else if (exception instanceof IOException) {throw (IOException) exception;}if (log.isDebugEnabled())log.debug("Unreported exception in load() " + exception);}} else {doLoad();}}
?最后调用了#doLoad()方法来具体的载入Session,源代码如下:
protected void doLoad() throws ClassNotFoundException, IOException {if (log.isDebugEnabled())log.debug("Start: Loading persisted sessions");// 清空Mapsessions.clear();// 对应work/Catalina/localhost/%app name%/SESSIONS.ser文件File file = file();if (file == null)return;if (log.isDebugEnabled())log.debug(sm.getString("standardManager.loading", pathname));FileInputStream fis = null;ObjectInputStream ois = null;Loader loader = null;ClassLoader classLoader = null;try {// 载入Session缓存文件fis = new FileInputStream(file.getAbsolutePath());BufferedInputStream bis = new BufferedInputStream(fis);if (container != null)loader = container.getLoader();if (loader != null)classLoader = loader.getClassLoader();if (classLoader != null) {if (log.isDebugEnabled())log.debug("Creating custom object input stream for class loader ");ois = new CustomObjectInputStream(bis, classLoader);} else {if (log.isDebugEnabled())log.debug("Creating standard object input stream");ois = new ObjectInputStream(bis);}} catch (FileNotFoundException e) {if (log.isDebugEnabled())log.debug("No persisted data file found");return;} catch (IOException e) {log.error(sm.getString("standardManager.loading.ioe", e), e);if (ois != null) {try {ois.close();} catch (IOException f) {;}ois = null;}throw e;}synchronized (sessions) {try {// 读出Session个数Integer count = (Integer) ois.readObject();int n = count.intValue();if (log.isDebugEnabled())log.debug("Loading " + n + " persisted sessions");//  读入Sessionfor (int i = 0; i < n; i++) {StandardSession session = getNewSession();session.readObjectData(ois);session.setManager(this);sessions.put(session.getIdInternal(), session);session.activate();sessionCounter++;}} catch (ClassNotFoundException e) {log.error(sm.getString("standardManager.loading.cnfe", e), e);if (ois != null) {try {ois.close();} catch (IOException f) {;}ois = null;}throw e;} catch (IOException e) {log.error(sm.getString("standardManager.loading.ioe", e), e);if (ois != null) {try {ois.close();} catch (IOException f) {;}ois = null;}throw e;} finally {try {if (ois != null)ois.close();} catch (IOException f) {}// 删除Session缓存文件if (file != null && file.exists())file.delete();}}if (log.isDebugEnabled())log.debug("Finish: Loading persisted sessions");}
?大致知道了Session的读取过程,后面就是Session没找到时创建Session的过程了。具体就是org.apache.catalina.session.StandardManager的#createSession()方法:
public Session createSession(String sessionId) {if ((maxActiveSessions >= 0) && (sessions.size() >= maxActiveSessions)) {rejectedSessions++;throw new IllegalStateException(sm.getString("standardManager.createSession.ise"));}return (super.createSession(sessionId));}
?最后调用到了它的基类的#createSession()方法了。
public Session createSession(String sessionId) {// 创建一个新的SessionSession session = createEmptySession();// 初始化Session的属性session.setNew(true);session.setValid(true);session.setCreationTime(System.currentTimeMillis());session.setMaxInactiveInterval(this.maxInactiveInterval);// 如果Session ID为null,那么就生成一个if (sessionId == null) {sessionId = generateSessionId();}session.setId(sessionId);sessionCounter++;return (session);}
protected void threadStart() { if (thread != null) return; if (backgroundProcessorDelay <= 0) return; threadDone = false; String threadName = "ContainerBackgroundProcessor[" + toString() + "]"; // 开启后台的监控线程 thread = new Thread(new ContainerBackgroundProcessor(), threadName); thread.setDaemon(true); thread.start(); }这个方法启动了一个ContainerBackgroundProcessor类的线程,这个类重写的#run()方法中包括了对Session的有效性监控。具体的细节就不详细陈述了。每隔一段时间,此线程就会启动一次并调用了ManageBase的#backgroundProcess()方法。其源代码如下:
public void backgroundProcess() {      count = (count + 1) % processExpiresFrequency;      if (count == 0)          processExpires();  }
每隔一段时间就会调用processExpires()方法去判断Session的有效性。
public void processExpires() {      // 现在的时间      long timeNow = System.currentTimeMillis();      // 所有的Session对象      Session sessions[] = findSessions();      int expireHere = 0;        if (log.isDebugEnabled())          log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount "                  + sessions.length);      for (int i = 0; i < sessions.length; i++) {          // 判断并设定Session是否失效          if (sessions[i] != null && !sessions[i].isValid()) {              expireHere++;          }      }      long timeEnd = System.currentTimeMillis();      if (log.isDebugEnabled())          log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow)                  + " expired sessions: " + expireHere);      processingTime += (timeEnd - timeNow);    }
此方法最终调用了isValid()去判断和设定Session是否失效,源代码如下所示:
public boolean isValid() { // 是否过期 if (this.expiring) { return true; } // 是否有效 if (!this.isValid) { return false; } // 正在使用中并且访问数大于0 if (ACTIVITY_CHECK && accessCount.get() > 0) { return true; } if (maxInactiveInterval >= 0) { // 判断Session是否过期 long timeNow = System.currentTimeMillis(); int timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L); if (timeIdle >= maxInactiveInterval) { // 设定Session过期 expire(true); } } return (this.isValid); } ?

热点排行