Tomcat的Session管理(一) - Session的生成
阅读原文时间:2021年04月20日阅读:1

Session对象的创建一般是源于这样的一条语句:
Session session = request.getSession(false);或者Session session = request.getSession();如果不在乎服务器压力可能多那么一点点的话。

在Tomcat的实现中,这个request是org.apache.catalina.connector.Request类的包装类org.apache.catalina.connector.RequestFacade的对象,它的两个#getSession()方法如下:

Java代码  

  1. public HttpSession getSession(boolean create) {

  2. if (request == null) {

  3. throw new IllegalStateException(

  4. sm.getString("requestFacade.nullRequest"));

  5. }

  6. if (SecurityUtil.isPackageProtectionEnabled()){

  7. return (HttpSession)AccessController.

  8. doPrivileged(new GetSessionPrivilegedAction(create));

  9. } else {

  10. return request.getSession(create);

  11. }

  12. }

Java代码  

  1. public HttpSession getSession() {

  2. if (request == null) {

  3. throw new IllegalStateException(

  4. sm.getString("requestFacade.nullRequest"));

  5. }

  6. return getSession(true);

  7. }

其实差不太多,最后都会进入org.apache.catalina.connector.Request的#getSession()方法。这个方法的源代码如下:

Java代码  

  1. public HttpSession getSession(boolean create) {
  2. Session session = doGetSession(create);
  3. if (session != null) {
  4. return session.getSession();
  5. } else {
  6. return null;
  7. }
  8. }

然后调用就到了#doGetSession()这个方法了。源代码如下

Java代码  

  1. protected Session doGetSession(boolean create) {

  2. // 没有Context的话直接返回null

  3. if (context == null)

  4. return (null);

  5. // 判断Session是否有效

  6. if ((session != null) && !session.isValid())

  7. session = null;

  8. if (session != null)

  9. return (session);

  10. // 返回Manager对象,这里是StandardManager类的对象

  11. Manager manager = null;

  12. if (context != null)

  13. manager = context.getManager();

  14. if (manager == null)

  15. return (null); // Sessions are not supported

  16. // 判断是否有SessionID

  17. if (requestedSessionId != null) {

  18. try {

  19. // 在Manager中根据SessionID查找Session

  20. session = manager.findSession(requestedSessionId);

  21. } catch (IOException e) {

  22. session = null;

  23. }

  24. if ((session != null) && !session.isValid())

  25. session = null;

  26. if (session != null) {

  27. // 更新访问时间

  28. session.access();

  29. return (session);

  30. }

  31. }

  32. // 创建新的Session

  33. if (!create)

  34. return (null);

  35. if ((context != null) && (response != null) && context.getCookies()

  36. && response.getResponse().isCommitted()) {

  37. throw new IllegalStateException(sm.getString("coyoteRequest.sessionCreateCommitted"));

  38. }

  39. // 判断是否使用 "/" 作为Session Cookie的存储路径 并且 是否SessionID来自Cookie

  40. if (connector.getEmptySessionPath() && isRequestedSessionIdFromCookie()) {

  41. // 创建Session

  42. session = manager.createSession(getRequestedSessionId());

  43. } else {

  44. session = manager.createSession(null);

  45. }

  46. // 创建一个新的Session Cookies

  47. if ((session != null) && (getContext() != null) && getContext().getCookies()) {

  48. Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME, session.getIdInternal());

  49. // 配置Session Cookie

  50. configureSessionCookie(cookie);

  51. // 在响应中加入Session Cookie

  52. response.addCookieInternal(cookie);

  53. }

  54. if (session != null) {

  55. // 更新访问时间

  56. session.access();

  57. return (session);

  58. } else {

  59. return (null);

  60. }

  61. }

这个方法说明了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进行了提取,源代码分别如下:

Java代码  

  1. protected void parseSessionId(org.apache.coyote.Request req, Request request) {

  2. ByteChunk uriBC = req.requestURI().getByteChunk();

  3. // 判断URL中是不是有";jsessionid="这个字符串

  4. int semicolon = uriBC.indexOf(match, 0, match.length(), 0);

  5. if (semicolon > 0) {

  6. // Parse session ID, and extract it from the decoded request URI

  7. // 在URL中提取Session ID

  8. int start = uriBC.getStart();

  9. int end = uriBC.getEnd();

  10. int sessionIdStart = semicolon + match.length();

  11. int semicolon2 = uriBC.indexOf(';', sessionIdStart);

  12. if (semicolon2 >= 0) {

  13. request.setRequestedSessionId(new String(uriBC.getBuffer(), start + sessionIdStart,

  14. semicolon2 - sessionIdStart));

  15. byte[] buf = uriBC.getBuffer();

  16. for (int i = 0; i < end - start - semicolon2; i++) {

  17. buf[start + semicolon + i] = buf[start + i + semicolon2];

  18. }

  19. uriBC.setBytes(buf, start, end - start - semicolon2 + semicolon);

  20. } else {

  21. request.setRequestedSessionId(new String(uriBC.getBuffer(), start + sessionIdStart,

  22. (end - start) - sessionIdStart));

  23. uriBC.setEnd(start + semicolon);

  24. }

  25. // 设定Session ID来自于URL

  26. request.setRequestedSessionURL(true);

  27. } else {

  28. request.setRequestedSessionId(null);

  29. request.setRequestedSessionURL(false);

  30. }

  31. }

Java代码  

  1. protected void parseSessionCookiesId(org.apache.coyote.Request req, Request request) {

  2. Context context = (Context) request.getMappingData().context;

  3. if (context != null && !context.getCookies())

  4. return;

  5. // 返回Cookie

  6. Cookies serverCookies = req.getCookies();

  7. int count = serverCookies.getCookieCount();

  8. if (count <= 0)

  9. return;

  10. for (int i = 0; i < count; i++) {

  11. ServerCookie scookie = serverCookies.getCookie(i);

  12. // 判断是否有JSESSIONID这个名字的Cookie

  13. if (scookie.getName().equals(Globals.SESSION_COOKIE_NAME)) {

  14. // Override anything requested in the URL

  15. if (!request.isRequestedSessionIdFromCookie()) {

  16. // 设定Session ID

  17. convertMB(scookie.getValue());

  18. request.setRequestedSessionId(scookie.getValue().toString());

  19. // 如果之前在URL中读到了SessionID,那么会覆盖它

  20. request.setRequestedSessionCookie(true);

  21. request.setRequestedSessionURL(false);

  22. if (log.isDebugEnabled())

  23. log.debug(" Requested cookie session id is " + request.getRequestedSessionId());

  24. } else {

  25. if (!request.isRequestedSessionIdValid()) {

  26. convertMB(scookie.getValue());

  27. request.setRequestedSessionId(scookie.getValue().toString());

  28. }

  29. }

  30. }

  31. }

  32. }

Tomcat就是通过上边的两个方法读到URL或者Cookie中存放的Session ID的。

了解了Session ID的获取,下面要看一下Session的查找过程,就是org.apache.catalina.session.StandardManager的#findSession()方法。这个方法是在它的基类中定义的,源代码如下:

Java代码  

  1. public Session findSession(String id) throws IOException {
  2. if (id == null)
  3. return (null);
  4. return (Session) sessions.get(id);
  5. }

代码很短,其中sessions是一个ConcurrentHashMap对象。那么这个sessions的对象是什么时候载入的Session呢?

启动的时候!可以看一下StandardManager#start()方法。最后调用了#load()方法,这个就是载入Session的方法了:

Java代码  

  1. public void load() throws ClassNotFoundException, IOException {
  2. if (SecurityUtil.isPackageProtectionEnabled()) {
  3. try {
  4. AccessController.doPrivileged(new PrivilegedDoLoad());
  5. } catch (PrivilegedActionException ex) {
  6. Exception exception = ex.getException();
  7. if (exception instanceof ClassNotFoundException) {
  8. throw (ClassNotFoundException) exception;
  9. } else if (exception instanceof IOException) {
  10. throw (IOException) exception;
  11. }
  12. if (log.isDebugEnabled())
  13. log.debug("Unreported exception in load() " + exception);
  14. }
  15. } else {
  16. doLoad();
  17. }
  18. }

最后调用了#doLoad()方法来具体的载入Session,源代码如下:

Java代码  

  1. protected void doLoad() throws ClassNotFoundException, IOException {

  2. if (log.isDebugEnabled())

  3. log.debug("Start: Loading persisted sessions");

  4. // 清空Map

  5. sessions.clear();

  6. // 对应work/Catalina/localhost/%app name%/SESSIONS.ser文件

  7. File file = file();

  8. if (file == null)

  9. return;

  10. if (log.isDebugEnabled())

  11. log.debug(sm.getString("standardManager.loading", pathname));

  12. FileInputStream fis = null;

  13. ObjectInputStream ois = null;

  14. Loader loader = null;

  15. ClassLoader classLoader = null;

  16. try {

  17. // 载入Session缓存文件

  18. fis = new FileInputStream(file.getAbsolutePath());

  19. BufferedInputStream bis = new BufferedInputStream(fis);

  20. if (container != null)

  21. loader = container.getLoader();

  22. if (loader != null)

  23. classLoader = loader.getClassLoader();

  24. if (classLoader != null) {

  25. if (log.isDebugEnabled())

  26. log.debug("Creating custom object input stream for class loader ");

  27. ois = new CustomObjectInputStream(bis, classLoader);

  28. } else {

  29. if (log.isDebugEnabled())

  30. log.debug("Creating standard object input stream");

  31. ois = new ObjectInputStream(bis);

  32. }

  33. } catch (FileNotFoundException e) {

  34. if (log.isDebugEnabled())

  35. log.debug("No persisted data file found");

  36. return;

  37. } catch (IOException e) {

  38. log.error(sm.getString("standardManager.loading.ioe", e), e);

  39. if (ois != null) {

  40. try {

  41. ois.close();

  42. } catch (IOException f) {

  43. ;

  44. }

  45. ois = null;

  46. }

  47. throw e;

  48. }

  49. synchronized (sessions) {

  50. try {

  51. // 读出Session个数

  52. Integer count = (Integer) ois.readObject();

  53. int n = count.intValue();

  54. if (log.isDebugEnabled())

  55. log.debug("Loading " + n + " persisted sessions");

  56. //  读入Session

  57. for (int i = 0; i < n; i++) {

  58. StandardSession session = getNewSession();

  59. session.readObjectData(ois);

  60. session.setManager(this);

  61. sessions.put(session.getIdInternal(), session);

  62. session.activate();

  63. sessionCounter++;

  64. }

  65. } catch (ClassNotFoundException e) {

  66. log.error(sm.getString("standardManager.loading.cnfe", e), e);

  67. if (ois != null) {

  68. try {

  69. ois.close();

  70. } catch (IOException f) {

  71. ;

  72. }

  73. ois = null;

  74. }

  75. throw e;

  76. } catch (IOException e) {

  77. log.error(sm.getString("standardManager.loading.ioe", e), e);

  78. if (ois != null) {

  79. try {

  80. ois.close();

  81. } catch (IOException f) {

  82. ;

  83. }

  84. ois = null;

  85. }

  86. throw e;

  87. } finally {

  88. try {

  89. if (ois != null)

  90. ois.close();

  91. } catch (IOException f) {

  92. }

  93. // 删除Session缓存文件

  94. if (file != null && file.exists())

  95. file.delete();

  96. }

  97. }

  98. if (log.isDebugEnabled())

  99. log.debug("Finish: Loading persisted sessions");

  100. }

大致知道了Session的读取过程,后面就是Session没找到时创建Session的过程了。具体就是org.apache.catalina.session.StandardManager的#createSession()方法:

Java代码  

  1. public Session createSession(String sessionId) {
  2. if ((maxActiveSessions >= 0) && (sessions.size() >= maxActiveSessions)) {
  3. rejectedSessions++;
  4. throw new IllegalStateException(sm.getString("standardManager.createSession.ise"));
  5. }
  6. return (super.createSession(sessionId));
  7. }

最后调用到了它的基类的#createSession()方法了。

Java代码  

  1. public Session createSession(String sessionId) {

  2. // 创建一个新的Session

  3. Session session = createEmptySession();

  4. // 初始化Session的属性

  5. session.setNew(true);

  6. session.setValid(true);

  7. session.setCreationTime(System.currentTimeMillis());

  8. session.setMaxInactiveInterval(this.maxInactiveInterval);

  9. // 如果Session ID为null,那么就生成一个

  10. if (sessionId == null) {

  11. sessionId = generateSessionId();

  12. }

  13. session.setId(sessionId);

  14. sessionCounter++;

  15. return (session);

  16. }

通过上述过程,一个新的Session就创建出来了。