tomcat容器通过一个称为Session管理器的组件来管理建立的Session对象,该组件由org.apache.catalina.Manager接口表示;Session管理器必须与一个Context容器相关联(需要用到Context容器的相关上下文或方法)。

默认情况下,Session管理器会将其所管理的 Session对象存放在内存中,不过在tomcat中,Session管理器也库将Session对象持久化,存储到文件存储器或通过JDBC写入到数据库中。

下面我们来分析具体实现,在servlet编程方面中,Session对象由javax.servlet.http.HttpSession接口表示;在tomcat中该接口的标准实现是org.apache.catalina.session包下的StandardSession类,该类同时实现了org.apache.catalina.Session接口,在tomcat内部供Session管理器使用;而实际交给servlet实例使用的是Session接口的外观类StandardSessionFacade

下面是Session管理器内部使用的Session接口

  1. public interface Session {
  2.  
  3. public static final String SESSION_CREATED_EVENT = "createSession";
  4.  
  5. public static final String SESSION_DESTROYED_EVENT = "destroySession";
  6.  
  7. public String getAuthType();
  8.  
  9. public void setAuthType(String authType);
  10.  
  11. public long getCreationTime();
  12.  
  13. public void setCreationTime(long time);
  14.  
  15. public String getId();
  16.  
  17. public void setId(String id);
  18.  
  19. public String getInfo();
  20.  
  21. public long getLastAccessedTime();
  22.  
  23. public Manager getManager();
  24.  
  25. public void setManager(Manager manager);
  26.  
  27. public int getMaxInactiveInterval();
  28.  
  29. public void setMaxInactiveInterval(int interval);
  30.  
  31. public void setNew(boolean isNew);
  32.  
  33. public Principal getPrincipal();
  34.  
  35. public void setPrincipal(Principal principal);
  36.  
  37. public HttpSession getSession();
  38.  
  39. public void setValid(boolean isValid);
  40.  
  41. public boolean isValid();
  42.  
  43. public void access();
  44.  
  45. public void addSessionListener(SessionListener listener);
  46.  
  47. public void expire();
  48.  
  49. public Object getNote(String name);
  50.  
  51. public Iterator getNoteNames();
  52.  
  53. public void recycle();
  54.  
  55. public void removeNote(String name);
  56.  
  57. public void removeSessionListener(SessionListener listener);
  58.  
  59. public void setNote(String name, Object value);
  60.  
  61. }

Session对象总是存在于Session管理器中,可以通过setManager()方法将Session实例 与某个Session管理器相关联;Session管理器可以通过setId()方法设置Session标识符;同时会调用getLastAccessedTime()方法判断一个Session对象的有效性;setValid()方法用于重置该Session对象的有效性;每当访问一个Session实例时,会调用access()方法来修改Session对象的最后访问时间;最后,Session管理器会调用Session对象的expire()方法使其过期,通过getSession()方法获取一个经过外观类StandardSessionFacade包装的HttpSession对象

StandardSession类是Session接口的标准实现,同时实现了javax.servlet.http.HttpSession接口和java.lang.Serializable接口

(注:StandardSession类实现HttpSession接口方法的实现基本上都依赖于实现Session接口的方法对StandardSession实例的填充,因此我们可以想象,StandardSession类基本上类似与适配器模式中的Adapter角色,实现了原类型(Session接口类型)到目标接口的转换(HttpSession接口))

其构造函数接受一个Manager接口的实例,迫使Session对象必须拥有一个Session管理器实例

  1. public StandardSession(Manager manager) {
  2.  
  3. super();
  4. this.manager = manager;
  5. if (manager instanceof ManagerBase)
  6. this.debug = ((ManagerBase) manager).getDebug();
  7.  
  8. }

下面是StandardSession实例的一些比较重要的私有成员变量

  1. //存储session的键值对
  2. private HashMap attributes = new HashMap();
  3. private transient String authType = null;
  4. //创建时间
  5. private long creationTime = 0L;
  6. //是否过期
  7. private transient boolean expiring = false;
  8. //外观类
  9. private transient StandardSessionFacade facade = null;
  10. //session标识
  11. private String id = null;
  12. //最后访问时间
  13. private long lastAccessedTime = creationTime;
  14. //监听器聚集
  15. private transient ArrayList listeners = new ArrayList();
  16. //session管理器
  17. private Manager manager = null;
  18. //清理session过期时间
  19. private int maxInactiveInterval = -1;
  20. private boolean isNew = false;
  21. private boolean isValid = false;
  22. //当前访问时间
  23. private long thisAccessedTime = creationTime;

其中getSession()方法通过传入自身实例来创建外观类StandardSessionFacade实例

  1. public HttpSession getSession() {
  2.  
  3. if (facade == null)
  4. facade = new StandardSessionFacade(this);
  5. return (facade);
  6.  
  7. }

如果session管理器中的某个Session对象在某个时间长度内都没有被访问的话,会被Session管理器设置为过期,这个时间长度是由变量maxInactiveInterval的值来指定

  1. public void expire(boolean notify) {
  2.  
  3. // Mark this session as "being expired" if needed
  4. if (expiring)
  5. return;
  6. expiring = true;
  7. setValid(false);
  8.  
  9. // Remove this session from our manager's active sessions
  10. if (manager != null)
  11. manager.remove(this);
  12.  
  13. // Unbind any objects associated with this session
  14. String keys[] = keys();
  15. for (int i = 0; i < keys.length; i++)
  16. removeAttribute(keys[i], notify);
  17.  
  18. // Notify interested session event listeners
  19. if (notify) {
  20. fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);
  21. }
  22.  
  23. // Notify interested application event listeners
  24. // FIXME - Assumes we call listeners in reverse order
  25. Context context = (Context) manager.getContainer();
  26. Object listeners[] = context.getApplicationListeners();
  27. if (notify && (listeners != null)) {
  28. HttpSessionEvent event =
  29. new HttpSessionEvent(getSession());
  30. for (int i = 0; i < listeners.length; i++) {
  31. int j = (listeners.length - 1) - i;
  32. if (!(listeners[j] instanceof HttpSessionListener))
  33. continue;
  34. HttpSessionListener listener =
  35. (HttpSessionListener) listeners[j];
  36. try {
  37. fireContainerEvent(context,
  38. "beforeSessionDestroyed",
  39. listener);
  40. listener.sessionDestroyed(event);
  41. fireContainerEvent(context,
  42. "afterSessionDestroyed",
  43. listener);
  44. } catch (Throwable t) {
  45. try {
  46. fireContainerEvent(context,
  47. "afterSessionDestroyed",
  48. listener);
  49. } catch (Exception e) {
  50. ;
  51. }
  52. // FIXME - should we do anything besides log these?
  53. log(sm.getString("standardSession.sessionEvent"), t);
  54. }
  55. }
  56. }
  57.  
  58. // We have completed expire of this session
  59. expiring = false;
  60. if ((manager != null) && (manager instanceof ManagerBase)) {
  61. recycle();
  62. }
  63.  
  64. }

上面方法中从Session管理器移除该session实例并触发一些事件,同时设置一些内部变量的值。

为了传递一个session对象给servlet实例,tomcat的session管理器会实例化StandardSession类,并填充该session对象;不过,为了阻止servlet程序员访问StandardSession实例中的一些敏感方法,tomcat容器将StandardSession实例封装为StandardSessionFacade类型的实例(实现HttpRequest接口的HttpRequestBase类的doGetSession方法里面调用session管理器的createSession()方法返回StandardSession类的实例,然后调用StandardSession实例 的getSession()方法返回StandardSessionFacade类型实例),该类仅仅实现了javax.servlet.http.HttpSession接口中的方法,这样servlet程序员就不能将HttpSession对象向下转型为StandardSession类型

下面接下来描述session管理器,session管理器是org.apache.catalina.Manager接口的实例,抽象类ManagerBase实现了Manager接口,提供了常见功能的实现;ManagerBase类有两个直接子类,分别为StandardManager类和PersistentManagerBase类

当tomcat运行时,StandardManager实例将session对象存储在内存中;当tomcat关闭时,它会将当前内存中所有的session对象序列化存储到文件中;当在此运行tomcat时,又会将这些session对象重新载入内存。

另外,继承自PersistentManagerBase类的session管理器会将session对象存储到辅助存储器中,包括PersistentManager类和DistributedManager类(tomcat4中)

下面是Manager接口的方法声明:

  1. public interface Manager {
  2.  
  3. public Container getContainer();
  4.  
  5. public void setContainer(Container container);
  6.  
  7. public DefaultContext getDefaultContext();
  8.  
  9. public void setDefaultContext(DefaultContext defaultContext);
  10.  
  11. public boolean getDistributable();
  12.  
  13. public void setDistributable(boolean distributable);
  14.  
  15. public String getInfo();
  16.  
  17. public int getMaxInactiveInterval();
  18.  
  19. public void setMaxInactiveInterval(int interval);
  20.  
  21. public void add(Session session);
  22.  
  23. public void addPropertyChangeListener(PropertyChangeListener listener);
  24.  
  25. public Session createSession();
  26.  
  27. public Session findSession(String id) throws IOException;
  28.  
  29. public Session[] findSessions();
  30.  
  31. public void load() throws ClassNotFoundException, IOException;
  32.  
  33. public void remove(Session session);
  34.  
  35. public void removePropertyChangeListener(PropertyChangeListener listener);
  36.  
  37. public void unload() throws IOException;
  38.  
  39. }

提供了setContainer()方法使用session管理器与Context容器相关联;一起一下添加、移除session实例的方法;设置session对象的最长存活时间;最后, load()方法与unload()方法用于从辅助存储器加载session对象到内存和序列化session对象并持久化到辅助存储器中。

ManagerBase类为一个抽象类,提供了一些公共方法的实现,包括创建session对象、移除session对象等;这些活动的session对象都存储在一个名为sessions的HashMap变量中

protected HashMap sessions = new HashMap();

下面是创建session实例的方法

  1. public Session createSession() {
  2.  
  3. // Recycle or create a Session instance
  4. Session session = null;
  5. synchronized (recycled) {
  6. int size = recycled.size();
  7. if (size > 0) {
  8. session = (Session) recycled.get(size - 1);
  9. recycled.remove(size - 1);
  10. }
  11. }
  12. if (session != null)
  13. session.setManager(this);
  14. else
  15. session = new StandardSession(this);
  16.  
  17. // Initialize the properties of the new session and return it
  18. session.setNew(true);
  19. session.setValid(true);
  20. session.setCreationTime(System.currentTimeMillis());
  21. session.setMaxInactiveInterval(this.maxInactiveInterval);
  22. String sessionId = generateSessionId();
  23. String jvmRoute = getJvmRoute();
  24. // @todo Move appending of jvmRoute generateSessionId()???
  25. if (jvmRoute != null) {
  26. sessionId += '.' + jvmRoute;
  27. session.setId(sessionId);
  28. }
  29. /*
  30. synchronized (sessions) {
  31. while (sessions.get(sessionId) != null) // Guarantee uniqueness
  32. sessionId = generateSessionId();
  33. }
  34. */
  35. session.setId(sessionId);
  36.  
  37. return (session);
  38.  
  39. }

上面创建的是StandardSession类型实例,在实现HttpRequest接口的HttpRequestBase类的相关方法中,调用getSession()方法返回StandardSessionFacade类型实例

创建session对象需要调用受保护的方法返回session对象的唯一标识符

  1. /**
  2. * Generate and return a new session identifier.
  3. */
  4. protected synchronized String generateSessionId() {
  5.  
  6. // Generate a byte array containing a session identifier
  7. Random random = getRandom();
  8. byte bytes[] = new byte[SESSION_ID_BYTES];
  9. getRandom().nextBytes(bytes);
  10. bytes = getDigest().digest(bytes);
  11.  
  12. // Render the result as a String of hexadecimal digits
  13. StringBuffer result = new StringBuffer();
  14. for (int i = 0; i < bytes.length; i++) {
  15. byte b1 = (byte) ((bytes[i] & 0xf0) >> 4);
  16. byte b2 = (byte) (bytes[i] & 0x0f);
  17. if (b1 < 10)
  18. result.append((char) ('0' + b1));
  19. else
  20. result.append((char) ('A' + (b1 - 10)));
  21. if (b2 < 10)
  22. result.append((char) ('0' + b2));
  23. else
  24. result.append((char) ('A' + (b2 - 10)));
  25. }
  26. return (result.toString());
  27.  
  28. }

该标识符生成之后,会发送到客户端cookies里面,使客户端cookies里面的JSESSIONID值与之一致

其他相关操作session对象方法如下,容易理解

  1. public void add(Session session) {
  2.  
  3. synchronized (sessions) {
  4. sessions.put(session.getId(), session);
  5. }
  6.  
  7. }
  8.  
  9. public Session findSession(String id) throws IOException {
  10.  
  11. if (id == null)
  12. return (null);
  13. synchronized (sessions) {
  14. Session session = (Session) sessions.get(id);
  15. return (session);
  16. }
  17.  
  18. }
  19.  
  20. public Session[] findSessions() {
  21.  
  22. Session results[] = null;
  23. synchronized (sessions) {
  24. results = new Session[sessions.size()];
  25. results = (Session[]) sessions.values().toArray(results);
  26. }
  27. return (results);
  28.  
  29. }
  30.  
  31. public void remove(Session session) {
  32.  
  33. synchronized (sessions) {
  34. sessions.remove(session.getId());
  35. }
  36.  
  37. }

StandardManager类是Manager接口的标准实现,继承自上面的ManagerBase抽象类,同时实现了Lifecycle接口,这有可以由其相关联的Context容器来启动和关闭,在Context容器调用它的start()方法和stop()方法时,会调用load()从辅助存储器加载session对象到内存和调用unload()方法从内存持久化session对象到辅助存储器

同时session管理器还负责销毁那些失效的session对象,这是由一个专门的线程来实现的,StandardManager类实现了Runnable接口

  1. /**
  2. * The background thread that checks for session timeouts and shutdown.
  3. */
  4. public void run() {
  5.  
  6. // Loop until the termination semaphore is set
  7. while (!threadDone) {
  8. threadSleep();
  9. processExpires();
  10. }
  11.  
  12. }

在线程休眠指定时间间隔后,调用processExpires()方法清理过期session对象

  1. /**
  2. * Invalidate all sessions that have expired.
  3. */
  4. private void processExpires() {
  5.  
  6. long timeNow = System.currentTimeMillis();
  7. Session sessions[] = findSessions();
  8.  
  9. for (int i = 0; i < sessions.length; i++) {
  10. StandardSession session = (StandardSession) sessions[i];
  11. if (!session.isValid())
  12. continue;
  13. int maxInactiveInterval = session.getMaxInactiveInterval();
  14. if (maxInactiveInterval < 0)
  15. continue;
  16. int timeIdle = // Truncate, do not round up
  17. (int) ((timeNow - session.getLastAccessedTime()) / 1000L);
  18. if (timeIdle >= maxInactiveInterval) {
  19. try {
  20. session.expire();
  21. } catch (Throwable t) {
  22. log(sm.getString("standardManager.expireException"), t);
  23. }
  24. }
  25. }
  26.  
  27. }

我们可以看到,session对象的过期时间实际是session对象本身的成员变量的值

int maxInactiveInterval = session.getMaxInactiveInterval()

最后,servlet程序员需要调用javax.servlet.http.HttpSerletRequest接口的getSession()方法获取Session对象,当调用getSession()方法时,request对象必须调用与Context容器相关联的session管理器(创建session对象或返回一个已存在色session对象);request对象为了能够访问Session管理器,它必须能够访问Context容器。因此在SimpleWrapperValve类的invoke()方法中,需要调用org.apache.catalina.Request接口的setContext()方法传入Context容器实例

  1. public void invoke(Request request, Response response, ValveContext valveContext)
  2. throws IOException, ServletException {
  3.  
  4. SimpleWrapper wrapper = (SimpleWrapper) getContainer();
  5. ServletRequest sreq = request.getRequest();
  6. ServletResponse sres = response.getResponse();
  7. Servlet servlet = null;
  8. HttpServletRequest hreq = null;
  9. if (sreq instanceof HttpServletRequest)
  10. hreq = (HttpServletRequest) sreq;
  11. HttpServletResponse hres = null;
  12. if (sres instanceof HttpServletResponse)
  13. hres = (HttpServletResponse) sres;
  14.  
  15. //-- new addition -----------------------------------
  16. Context context = (Context) wrapper.getParent();
  17. request.setContext(context);
  18. //-------------------------------------
  19. // Allocate a servlet instance to process this request
  20. try {
  21. servlet = wrapper.allocate();
  22. if (hres!=null && hreq!=null) {
  23. servlet.service(hreq, hres);
  24. }
  25. else {
  26. servlet.service(sreq, sres);
  27. }
  28. }
  29. catch (ServletException e) {
  30. }
  31. }

同时在org.apache.catalina.connector.HttpRequestBase类的私有方法diGetSession()里面会调用Context接口的getManager()方法来获取session管理器对象

  1. private HttpSession doGetSession(boolean create) {
  2. // There cannot be a session if no context has been assigned yet
  3. if (context == null)
  4. return (null);
  5.  
  6. // Return the current session if it exists and is valid
  7. if ((session != null) && !session.isValid())
  8. session = null;
  9. if (session != null)
  10. return (session.getSession());
  11.  
  12. // Return the requested session if it exists and is valid
  13. Manager manager = null;
  14. if (context != null)
  15. manager = context.getManager();
  16.  
  17. if (manager == null)
  18. return (null); // Sessions are not supported
  19.  
  20. if (requestedSessionId != null) {
  21. try {
  22. session = manager.findSession(requestedSessionId);
  23. } catch (IOException e) {
  24. session = null;
  25. }
  26. if ((session != null) && !session.isValid())
  27. session = null;
  28. if (session != null) {
  29. return (session.getSession());
  30. }
  31. }
  32.  
  33. // Create a new session if requested and the response is not committed
  34. if (!create)
  35. return (null);
  36. if ((context != null) && (response != null) &&
  37. context.getCookies() &&
  38. response.getResponse().isCommitted()) {
  39. throw new IllegalStateException
  40. (sm.getString("httpRequestBase.createCommitted"));
  41. }
  42.  
  43. session = manager.createSession();
  44. if (session != null)
  45. return (session.getSession());
  46. else
  47. return (null);
  48.  
  49. }

注意里面实质是通过StandardSession实例的getSession()方法获取一个经过外观类StandardSessionFacade包装的HttpSession对象

---------------------------------------------------------------------------

本系列How Tomcat Works系本人原创

转载请注明出处 博客园 刺猬的温驯

本人邮箱: chenying998179#163.com (#改为@)

本文链接http://www.cnblogs.com/chenying99/p/3237390.html

How Tomcat Works(十二)的更多相关文章

  1. How Tomcat Works(二十)

    要使用一个web应用程序,必须要将表示该应用程序的Context实例部署到一个host实例中.在tomcat中,context实例可以用war文件的形式来部署,也可以将整个web应用拷贝到Tomcat ...

  2. How Tomcat Works(二)

    我们这些可怜虫,只有沿着大神的思路,这样我们才能进步得更快:因为我们不是跟大神处于同一级别上.所以我这里是参考<How Tomcat Works>这本英文版的大作来理解tomcat的工作原 ...

  3. 攻城狮在路上(肆)How tomcat works(二) 一个简单的servlet容器

    该节在上一节的基础上增加了所谓对静态资源和动态资源访问的不同控制流程.示例里面采用的是对路径“/servlet/”进行了特殊处理. 一. 主要还是从HttpServer1中的main方法开始,先解析出 ...

  4. how tomcat works 总结 二

    第五章 servlet容器 第 5 章讨论 container 模块.container 指的是 org.apache.catalina.Container 接口,有4 种类型的 container: ...

  5. How Tomcat Works(十四)补充

    在How Tomcat Works(十四)中,本人并没有对javax.servlet.Filter及javax.servlet.FilterChain做详细的描述,本文在这里做一下补充 FilterC ...

  6. How Tomcat Works(十四)

    我们已经知道,在tomcat中有四种类型的servlet容器,分别为Engine.Host.Context 和Wrapper,本文接下来对tomcat中Wrapper接口的标准实现进行说明. 对于每个 ...

  7. How Tomcat Works(十八)

    在前面的文章中,如果我们要启动tomcat容器,我们需要使用Bootstrap类来实例化连接器.servlet容器.Wrapper实例和其他组件,然后调用各个对象的set方法将它们关联起来:这种配置应 ...

  8. How Tomcat Works(十六)

    本文接下来会介绍Host容器和Engine容器,在tomcat的实际部署中,总是会使用一个Host容器:本文介绍Host接口和Engine接口及其相关类 Host容器是org.apache.catal ...

  9. How Tomcat Works(十五)

    本文接下来分析Context容器,Context容器实例表示一个具体的Web应用程序,其中包括一个或多个Wrapper实例:不过Context容器还需要其他的组件支持,典型的如载入器和Session管 ...

随机推荐

  1. 旧书重温:0day2【2】 实验:三种获取kernel32.dll基址的方法

    0x01 找kernel32基地址的方法一般有三种: 暴力搜索法.异常处理链表搜索法.PEB法. 0x02 基本原理 暴力搜索法是最早的动态查找kernel32基地址的方法.它的原理是几乎所有的win ...

  2. java金额的加减乘除

    package com.wedge.edp.framework.common.util; import java.math.BigDecimal; /** * 金额的加减乘除 */ public cl ...

  3. 部署HBase远程访问的问题集合(Eclipse)

    实现远程访问HBase,可以通过Eclipse开发工具方便进行代码调试. 为了方便jar包各种版本的管理,才用maven进行代码构建 首先,下载并安装maven以及M2Eclipse插件 其次,配置m ...

  4. [转] gc tips(2)

    原文地址:http://kevincao.com/2011/08/actionscript-garbage-collection-1/ 谈谈ActionScript垃圾回收(上) 在<给AS程序 ...

  5. mysql 查看所有存储过程

    转载地址:http://zhuixue.iteye.com/blog/375353 查询数据库中的存储过程 方法一: select `name` from mysql.proc where db = ...

  6. 实现LoaderCallbacks接口动态循环加载网上图片并展示在手机屏幕上 ...

    1.布局xml文件 activity_main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/re ...

  7. MVC-READ5(asp.net web from PK asp.net MVC)

    webform: ViewState问题 页面生命周期 不能很好的分解关注点 对HTML操控受限 抽象不完全 可测试性弱

  8. Modernizr.js入门指南(HTML5&CSS3浏览器兼容插件)

    HTML5 和 CSS3 的快速发展,给我们带来了极大的便利,比如从此再也不用花费大量的时间只是为了设计一个圆角的效果. 但是!我们不能像控制机器一样来控制所有的人都一夜之间升级到现代浏览器,因为那些 ...

  9. Android开发中这些小技巧

    http://blog.csdn.net/guxiao1201/article/details/40655661 http://blog.csdn.net/guxiao1201/article/det ...

  10. 在VMware虚拟机中安装CentOS 7

    [声明] 欢迎转载,但请保留文章原始出处 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/3917 ...