版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/beliefer/article/details/52451061

前言

  在《Tomcat7.0源码分析——Session管理分析(上)》一文中我介绍了Session、Session管理器,还以StandardManager为例介绍了Session管理器的初始化与启动,本文将接着介绍Session管理的其它内容。

Session分配

  在《Tomcat7.0源码分析——请求原理分析(下)》一文的最后我们介绍了Filter的职责链,Tomcat接收到的请求会经过Filter职责链,最后交给具体的Servlet处理。以访问http://localhost:8080/host-manager这个路径为例,可以清楚的看到整个调用栈(如图1所示,由于原图本人没有妥善保存,只剩下了这张打过水印的)中的Filter的职责链及之后的JspServlet,最后到达org.apache.catalina.connector.Request的getSession方法。

图1  请求调用栈

  Request的getSession方法(见代码清单1)用于获取当前请求对应的会话信息,如果没有则创建一个新的Session。

代码清单1

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

doGetSession方法的实现见代码清单2。

代码清单2

    protected Session doGetSession(boolean create) {// There cannot be a session if no context has been assigned yetif (context == null)return (null);// Return the current session if it exists and is validif ((session != null) && !session.isValid())session = null;if (session != null)return (session);// Return the requested session if it exists and is validManager manager = null;if (context != null)manager = context.getManager();if (manager == null)return (null);      // Sessions are not supportedif (requestedSessionId != null) {try {session = manager.findSession(requestedSessionId);} catch (IOException e) {session = null;}if ((session != null) && !session.isValid())session = null;if (session != null) {session.access();return (session);}}// Create a new session if requested and the response is not committedif (!create)return (null);if ((context != null) && (response != null) &&context.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.COOKIE) &&response.getResponse().isCommitted()) {throw new IllegalStateException(sm.getString("coyoteRequest.sessionCreateCommitted"));}// Attempt to reuse session id if one was submitted in a cookie// Do not reuse the session id if it is from a URL, to prevent possible// phishing attacks// Use the SSL session ID if one is present. if (("/".equals(context.getSessionCookiePath()) && isRequestedSessionIdFromCookie()) || requestedSessionSSL ) {session = manager.createSession(getRequestedSessionId());} else {session = manager.createSession(null);}// Creating a new session cookie based on that sessionif ((session != null) && (getContext() != null)&& getContext().getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.COOKIE)) {Cookie cookie =ApplicationSessionCookieConfig.createSessionCookie(context, session.getIdInternal(), isSecure());response.addSessionCookieInternal(cookie);}if (session == null) {return null;}session.access();return session;}

依据代码清单2,整个获取Session的步骤如下:

  1. 判断当前Request对象是否已经存在有效的Session信息,如果存在则返回此Session,否则进入下一步;
  2. 获取Session管理器,比如StandardManager;
  3. 从StandardManager的Session缓存中获取Session,如果有则返回此Session,否则进入下一步;
  4. 创建Session;
  5. 创建保存Session ID的Cookie;
  6. 通过调用Session的access方法更新Session的访问时间以及访问次数。

  我们来着重阅读ManagerBase实现的createSession方法,见代码清单3。

代码清单3

    public Session createSession(String sessionId) {// Recycle or create a Session instanceSession session = createEmptySession();// Initialize the properties of the new session and return itsession.setNew(true);session.setValid(true);session.setCreationTime(System.currentTimeMillis());session.setMaxInactiveInterval(this.maxInactiveInterval);if (sessionId == null) {sessionId = generateSessionId();}session.setId(sessionId);sessionCounter++;return (session);}

至此,Session的创建与分配就介绍这些。

Session追踪

  HTTP是一种无状态的协议,如果一个客户端只是单纯地请求一个文件,服务器端并不需要知道一连串的请求是否来自于相同的客户端,而且也不需要担心客户端是否处在连接状态。但是这样的通信协议使得服务器端难以判断所连接的客户端是否是同一个人。当进行Web程序开发时,我们必须想办法将相关的请求结合一起,并且努力维持用户的状态在服务器上,这就引出了会话追踪(session tracking)。

  Tomcat追踪Session主要借助其ID,因此在接收到请求后应该需要拿到此请求对应的会话ID,这样才能够和StandardManager的缓存中维护的Session相匹配,达到Session追踪的效果。还记得《Tomcat7.0源码分析——请求原理分析(中)》一文中介绍CoyoteAdapter的service方法时调用的postParseRequest方法吗?其中有这么一段代码,见代码清单4。

代码清单4

    if (request.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.URL)) {// Get the session ID if there was oneString sessionID = request.getPathParameter(ApplicationSessionCookieConfig.getSessionUriParamName(request.getContext()));if (sessionID != null) {request.setRequestedSessionId(sessionID);request.setRequestedSessionURL(true);}}// 省去中间无关代码// Finally look for session ID in cookies and SSL sessionparseSessionCookiesId(req, request);parseSessionSslId(request);return true;}

根据代码清单4可以看出postParseRequest方法的执行步骤如下:

  1. 如果开启了会话跟踪(session tracking),则需要从缓存中获取维护的Session ID;
  2. 从请求所带的Cookie中获取Session ID;
  3. 如果Cookie没有携带Session ID,但是开启了会话跟踪(session tracking),则可以从SSL中获取Session ID;

从缓存中获取维护的Session ID

代码清单4中首先调用getSessionUriParamName方法(见代码清单5)获取Session的参数名称。

代码清单5

    public static String getSessionUriParamName(Context context) {String result = getConfiguredSessionCookieName(context);if (result == null) {result = DEFAULT_SESSION_PARAMETER_NAME; }return result; }

从代码清单2看出,getSessionUriParamName方法首先调用getConfiguredSessionCookieName方法获取Session的Cookie名称,如果没有则默认为jsessionid(常量DEFAULT_SESSION_PARAMETER_NAME的值)。回头看代码清单4中会以getSessionUriParamName方法返回的值作为request.getPathParameter(见代码清单6)的参数查询Session ID。

代码清单6

    protected String getPathParameter(String name) {return pathParameters.get(name);}

从请求所带的Cookie中获取Session ID

  代码清单4中调用的parseSessionCookiesId方法(见代码清单7)用来从Cookie中获取Session ID。

代码清单7

    protected void parseSessionCookiesId(org.apache.coyote.Request req, Request request) {// If session tracking via cookies has been disabled for the current// context, don't go looking for a session ID in a cookie as a cookie// from a parent context with a session ID may be present which would// overwrite the valid session ID encoded in the URLContext context = (Context) request.getMappingData().context;if (context != null && !context.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.COOKIE))return;// Parse session id from cookiesCookies serverCookies = req.getCookies();int count = serverCookies.getCookieCount();if (count <= 0)return;String sessionCookieName =ApplicationSessionCookieConfig.getSessionCookieName(context);for (int i = 0; i < count; i++) {ServerCookie scookie = serverCookies.getCookie(i);if (scookie.getName().equals(sessionCookieName)) {// Override anything requested in the URLif (!request.isRequestedSessionIdFromCookie()) {// Accept only the first session id cookieconvertMB(scookie.getValue());request.setRequestedSessionId(scookie.getValue().toString());request.setRequestedSessionCookie(true);request.setRequestedSessionURL(false);if (log.isDebugEnabled())log.debug(" Requested cookie session id is " +request.getRequestedSessionId());} else {if (!request.isRequestedSessionIdValid()) {// Replace the session id until one is validconvertMB(scookie.getValue());request.setRequestedSessionId(scookie.getValue().toString());}}}}}

从SSL中获取Session ID

  代码清单4中调用的parseSessionSslId方法(见代码清单8)用来从SSL中获取Session ID。

代码清单8

    protected void parseSessionSslId(Request request) {if (request.getRequestedSessionId() == null &&SSL_ONLY.equals(request.getServletContext().getEffectiveSessionTrackingModes()) &&request.connector.secure) {// TODO Is there a better way to map SSL sessions to our sesison ID?// TODO The request.getAttribute() will cause a number of other SSL//      attribute to be populated. Is this a performance concern?request.setRequestedSessionId(request.getAttribute(SSLSupport.SESSION_ID_KEY).toString());request.setRequestedSessionSSL(true);}}

Session销毁

  在《Tomcat7.0源码分析——生命周期管理》一文中我们介绍了容器的生命周期管理相关的内容,StandardEngine作为容器,其启动过程中也会调用startInternal方法(见代码清单9)。

代码清单9

    @Overrideprotected synchronized void startInternal() throws LifecycleException {// Log our server identification informationif(log.isInfoEnabled())log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());// Standard container startupsuper.startInternal();}

StandardEngine的startInternal方法实际代理了父类ContainerBase的startInternal方法(见代码清单10)。

代码清单10

    @Overrideprotected synchronized void startInternal() throws LifecycleException {// Start our subordinate components, if anyif ((loader != null) && (loader instanceof Lifecycle))((Lifecycle) loader).start();logger = null;getLogger();if ((logger != null) && (logger instanceof Lifecycle))((Lifecycle) logger).start();if ((manager != null) && (manager instanceof Lifecycle))((Lifecycle) manager).start();if ((cluster != null) && (cluster instanceof Lifecycle))((Lifecycle) cluster).start();if ((realm != null) && (realm instanceof Lifecycle))((Lifecycle) realm).start();if ((resources != null) && (resources instanceof Lifecycle))((Lifecycle) resources).start();// Start our child containers, if anyContainer children[] = findChildren();for (int i = 0; i < children.length; i++) {children[i].start();}// Start the Valves in our pipeline (including the basic), if anyif (pipeline instanceof Lifecycle)((Lifecycle) pipeline).start();setState(LifecycleState.STARTING);// Start our threadthreadStart();}

代码清单10一开始对各种子容器进行了启动(由于与本文内容关系不大,所以不多作介绍),最后会调用threadStart方法。threadStart的实现见代码清单11。

代码清单11

    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();}

threadStart方法启动了一个后台线程,任务为ContainerBackgroundProcessor。ContainerBackgroundProcessor的run方法中主要调用了processChildren方法,见代码清单12。

代码清单12

    protected class ContainerBackgroundProcessor implements Runnable {public void run() {while (!threadDone) {try {Thread.sleep(backgroundProcessorDelay * 1000L);} catch (InterruptedException e) {// Ignore}if (!threadDone) {Container parent = (Container) getMappingObject();ClassLoader cl = Thread.currentThread().getContextClassLoader();if (parent.getLoader() != null) {cl = parent.getLoader().getClassLoader();}processChildren(parent, cl);}}}protected void processChildren(Container container, ClassLoader cl) {try {if (container.getLoader() != null) {Thread.currentThread().setContextClassLoader(container.getLoader().getClassLoader());}container.backgroundProcess();} catch (Throwable t) {log.error("Exception invoking periodic operation: ", t);} finally {Thread.currentThread().setContextClassLoader(cl);}Container[] children = container.findChildren();for (int i = 0; i < children.length; i++) {if (children[i].getBackgroundProcessorDelay() <= 0) {processChildren(children[i], cl);}}}}

processChildren方法会不断迭代StandardEngine的子容器并调用这些子容器的backgroundProcess方法。这里我们直接来看StandardEngine的孙子容器StandardManager的backgroundProcess实现,即ManagerBase的backgroundProcess方法,见代码清单13。

代码清单13

    public void backgroundProcess() {count = (count + 1) % processExpiresFrequency;if (count == 0)processExpires();}

backgroundProcess里实现了一个简单的算法:

  • count:计数器,起始为0;
  • processExpiresFrequency:执行processExpires方法的频率,默认为6。

每执行一次backgroundProcess方法,count会增加1,每当count+1与processExpiresFrequency求模等于0,则调用processExpires。简而言之,每执行processExpiresFrequency指定次数的backgroundProcess方法,执行一次processExpires方法。processExpires的实现见代码清单14所示。

代码清单14

    public void processExpires() {long timeNow = System.currentTimeMillis();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++) {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 );}

代码清单14中processExpires方法的执行步骤如下:

  1. 从缓存取出所有的Session;
  2. 逐个校验每个Session是否过期,对于已经过期的Session,则expireHere自增。

Session的标准实现是StandardSession,其isValid方法(见代码清单15)的主要功能是判断Session是否过期,对于过期的,则将其expiring状态改为true。判断过期的公式为:

( (当前时间 - Session的最后访问时间)/1000) >= 最大访问间隔

代码清单15

    public boolean isValid() {if (this.expiring) {return true;}if (!this.isValid) {return false;}if (ACTIVITY_CHECK && accessCount.get() > 0) {return true;}if (maxInactiveInterval >= 0) { long timeNow = System.currentTimeMillis();int timeIdle;if (LAST_ACCESS_AT_START) {timeIdle = (int) ((timeNow - lastAccessedTime) / 1000L);} else {timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);}if (timeIdle >= maxInactiveInterval) {expire(true);}}return (this.isValid);}

总结

  Tomcat对于Session的管理过程包括创建、分配、维护和跟踪、销毁等。

后记:个人总结整理的《深入理解Spark:核心思想与源码分析》一书现在已经正式出版上市,目前京东、当当、天猫等网站均有销售,欢迎感兴趣的同学购买。

京东:http://item.jd.com/11846120.html

当当:http://product.dangdang.com/23838168.html

Tomcat7.0源码分析——Session管理分析(下)相关推荐

  1. Tomcat7.0源码分析——Session管理分析(上)

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/beliefer/article/details/52450268 前言 对于广大java开发者而言, ...

  2. Tomcat7.0源码分析——请求原理分析(上)

    前言 谈起Tomcat的诞生,最早可以追溯到1995年.近20年来,Tomcat始终是使用最广泛的Web服务器,由于其使用Java语言开发,所以广为Java程序员所熟悉.很多早期的J2EE项目,由程序 ...

  3. Tomcat7.0源码分析——server.xml文件的加载与解析

    前言 作为Java程序员,对于Tomcat的server.xml想必都不陌生.本文基于Tomcat7.0的Java源码,对server.xml文件是如何加载和解析进行分析. 加载过程分析 Bootst ...

  4. Android4.0源码Launcher启动流程分析【android源码Launcher系列一】

    最近研究ICS4.0的Launcher,发现4.0和2.3有稍微点区别,但是区别不是特别大,所以我就先整理一下Launcher启动的大致流程. Launcher其实是贯彻于手机的整个系统的,时时刻刻都 ...

  5. Android中ICS4.0源码Launcher启动流程分析【android源码Launcher系列一】

    最近研究ICS4.0的Launcher,发现4.0和2.3有稍微点区别,但是区别不是特别大,所以我就先整理一下Launcher启动的大致流程.Launcher其实是贯彻于手机的整个系统的,时时刻刻都在 ...

  6. Android 7.0 源码分析项目一期竣工啦

    从 Android 入行开始,因为工作需求和解决疑难bug的原因陆陆续续的看过一些源码,但都不成系统,从2016年年底开始,在Github上建了一个Android Open Source Projec ...

  7. 本地编译Hadoop2.8.0源码总结和问题解决(转自:http://blog.csdn.net/young_kim1/article/details/50324345)

    编译准备 1.下载所需的软件 先去官网下载hadoop2.8.0源码并解压,打开解压目录下的BUILDING.txt,编译过程和需要的软件其实就是根据这个文档里的描述来的. (可以通过命令下载:wge ...

  8. RocketMQ4.0源码分析之-路由管理

    RocketMQ4.0源码分析之-路由管理 一 前言 路由管理功能是RocketMQ的核心功能之一,涵盖了订阅管理,连接管理,负载均衡管理等一系列功能,代码布在NameServer,Broker,Pr ...

  9. android6.0源码分析之AMS服务源码分析

    activitymanagerservice服务源码分析 1.ActivityManagerService概述 ActivityManagerService(以下简称AMS)作为Android中最核心 ...

最新文章

  1. Android 使用java 代码获取res 里面的value 定义的数组
  2. Deep Learning论文笔记之(三)单层非监督学习网络分析
  3. oracle输出查询返回多行,ORA-01427:单行子查询返回多行
  4. mysql 二次 聚合,MySql-聚合查询
  5. Java 虚拟机对锁优化所做的努力
  6. oracle 查看内存参数配置,Oracle内存参数配置及版本问题
  7. Android应用开发—ViewPager FragmentPagerAdapter和FragmentStatePagerAdapter下Fragment的生命周期
  8. MySQL表的非外键约束
  9. hdu 3641 数论 二分求符合条件的最小值数学杂题
  10. 【物理】概念的理解 —— 蒸馏、萃取
  11. 设置代理,多进程爬虫
  12. asBroadcastStream
  13. 创业公司一年工作总结!
  14. 阿里云服务器价格计算器有什么用
  15. 【git 常用配置及常用命令】
  16. Unity中摄像机跟随
  17. 陆奇-奇绩创坛-chatGPT新范式,新时代,新机会
  18. eclipes的安装与使用
  19. Kaggle泰坦尼克号生存预测挑战——数据分析
  20. C++基础 Data类的实现

热门文章

  1. Toby Walsh教授:四个指数趋势解释人工智能威胁论!
  2. SpringBoot学习(一)
  3. lvm讲解 磁盘故障小案例
  4. Qlik收购Idevio,为客户带来先进的地理信息分析功能
  5. Visual Studio 2017 15.7预览版发布
  6. 【PMP】知识点总结20170528
  7. input子系统分析(转)
  8. wordpress必装的插件 wp最常用的十个插件
  9. SQL Server游标的使用【转】
  10. 2018-2019-2 网络对抗技术 20165324 Exp4:恶意代码分析