上文通过源码,分析了session显式创建和销毁的流程,但有些时候,我们并没有亲自去创建session,但不代表它不存在,笔者将这种情况,称之为隐式创建和销毁。

一、创建流程

    这里介绍session隐式创建的一种情况,即jsp的执行过程。由于jsp内置9个对象,其中就有session,在不禁用session的情况下(<%@page session="false"%>可以禁止创建session),就会创建session。jsp内置对象的讲解可以查看下面的文章:

    JSP九大内置对象及四个作用域JSP执行过程详解

    下面还是从源码角度,分析下session的创建过程

    1、在eclipse里面,创建一个最简单的jsp,命名为index2.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

</body>
</html>

    2、在浏览器中访问这个jsp:http://127.0.0.1:8080/index2.jsp

session销毁方法 jsp销毁session_html

    上图可以看到,确实创建session了。由于jsp在第一次运行的时候,会由tomcat的jsp执行引擎,生成对应的servlet代码,并编译为.class,我们到tomcat项目目录下找这个java类:{you web}/_/org/apache/jsp/index2_jsp.java

/*
 * Generated by the Jasper component of Apache Tomcat
 * Version: Apache Tomcat/7.0.56
 * Generated at: 2016-01-19 08:03:21 UTC
 * Note: The last modified time of this file was set to
 *       the last modified time of the source file after
 *       generation to assist with modification tracking.
 */
package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;

public final class index2_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent {

  private static final javax.servlet.jsp.JspFactory _jspxFactory =
          javax.servlet.jsp.JspFactory.getDefaultFactory();

  private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants;

  private javax.el.ExpressionFactory _el_expressionfactory;
  private org.apache.tomcat.InstanceManager _jsp_instancemanager;

  public java.util.Map<java.lang.String,java.lang.Long> getDependants() {
    return _jspx_dependants;
  }

  public void _jspInit() {
    _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
    _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig());
  }

  public void _jspDestroy() {
  }

  public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
        throws java.io.IOException, javax.servlet.ServletException {

    final javax.servlet.jsp.PageContext pageContext;
    javax.servlet.http.HttpSession session = null;
    final javax.servlet.ServletContext application;
    final javax.servlet.ServletConfig config;
    javax.servlet.jsp.JspWriter out = null;
    final java.lang.Object page = this;
    javax.servlet.jsp.JspWriter _jspx_out = null;
    javax.servlet.jsp.PageContext _jspx_page_context = null;


    try {
      response.setContentType("text/html; charset=UTF-8");
      pageContext = _jspxFactory.getPageContext(this, request, response,
      			null, true, 8192, true);
      _jspx_page_context = pageContext;
      application = pageContext.getServletContext();
      config = pageContext.getServletConfig();
      session = pageContext.getSession();
      out = pageContext.getOut();
      _jspx_out = out;

      out.write("\r\n");
      out.write("<!DOCTYPE html\">\r\n");
      out.write("<html>\r\n");
      out.write("<head>\r\n");
      out.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\r\n");
      out.write("<title>Insert title here</title>\r\n");
      out.write("</head>\r\n");
      out.write("<body>\r\n");
      out.write("\r\n");
      out.write("</body>\r\n");
      out.write("</html>");
    } catch (java.lang.Throwable t) {
      if (!(t instanceof javax.servlet.jsp.SkipPageException)){
        out = _jspx_out;
        if (out != null && out.getBufferSize() != 0)
          try {
            if (response.isCommitted()) {
              out.flush();
            } else {
              out.clearBuffer();
            }
          } catch (java.io.IOException e) {}
        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
        else throw new ServletException(t);
      }
    } finally {
      _jspxFactory.releasePageContext(_jspx_page_context);
    }
  }
}

    上面的代码第58行,session = pageContext.getSession();就是获取session了。index2_jsp extends org.apache.jasper.runtime.HttpJspBase,而HttpJspBase extends HttpServlet,说明index2_jsp就是一个servlet对象。jsp在运行的时候,会执行_jspService方法,看到代码首选获取了pageContext对象,而这个对象的创建,最终会调用:void org.apache.jasper.runtime.PageContextImpl._initialize(Servlet servlet, ServletRequest request, ServletResponse response, String errorPageURL, boolean needsSession, int bufferSize, boolean autoFlush),接下来看看是怎么初始化的:

private void _initialize(Servlet servlet, ServletRequest request,
                         ServletResponse response, String errorPageURL,
                         boolean needsSession, int bufferSize, boolean autoFlush) {

    // initialize state
    this.servlet = servlet;
    this.config = servlet.getServletConfig();
    this.context = config.getServletContext();
    this.errorPageURL = errorPageURL;
    this.request = request;
    this.response = response;

    // initialize application context
    this.applicationContext = JspApplicationContextImpl.getInstance(context);

    // Setup session (if required)
    if (request instanceof HttpServletRequest && needsSession)
        this.session = ((HttpServletRequest) request).getSession();
    if (needsSession && session == null)
        throw new IllegalStateException(
                "Page needs a session and none is available");

    // initialize the initial out ...
    depth = -1;
    if (bufferSize == JspWriter.DEFAULT_BUFFER) {
        bufferSize = Constants.DEFAULT_BUFFER_SIZE;
    }
    if (this.baseOut == null) {
        this.baseOut = new JspWriterImpl(response, bufferSize, autoFlush);
    } else {
        this.baseOut.init(response, bufferSize, autoFlush);
    }
    this.out = baseOut;

    // register names/values as per spec
    setAttribute(OUT, this.out);
    setAttribute(REQUEST, request);
    setAttribute(RESPONSE, response);

    if (session != null)
        setAttribute(SESSION, session);

    setAttribute(PAGE, servlet);
    setAttribute(CONFIG, config);
    setAttribute(PAGECONTEXT, this);
    setAttribute(APPLICATION, context);

    isIncluded = request.getAttribute(
            RequestDispatcher.INCLUDE_SERVLET_PATH) != null;
}

二、销毁流程

    上面的代码可以看到,在jsp执行完成后的finally块中,调用了释放page资源的代码:

_jspxFactory.releasePageContext(_jspx_page_context);下面看看这段代码做了什么:

@Override
public void releasePageContext(PageContext pc) {
    if( pc == null )
        return;
    if( Constants.IS_SECURITY_ENABLED ) {
        PrivilegedReleasePageContext dp = new PrivilegedReleasePageContext(
                this,pc);
        AccessController.doPrivileged(dp);
    } else {
        internalReleasePageContext(pc);
    }
}

这段代码最终调用了pageContext的release()方法:

@Override
public void release() {
    out = baseOut;
    try {
        if (isIncluded) {
            ((JspWriterImpl) out).flushBuffer();
            // push it into the including jspWriter
        } else {
            // Old code:
            // out.flush();
            // Do not flush the buffer even if we're not included (i.e.
            // we are the main page. The servlet will flush it and close
            // the stream.
            ((JspWriterImpl) out).flushBuffer();
        }
    } catch (IOException ex) {
        IllegalStateException ise = new IllegalStateException(Localizer.getMessage("jsp.error.flush"), ex);
        throw ise;
    } finally {
        servlet = null;
        config = null;
        context = null;
        applicationContext = null;
        elContext = null;
        errorPageURL = null;
        request = null;
        response = null;
        depth = -1;
        baseOut.recycle();
        session = null;
        attributes.clear();
        for (BodyContentImpl body: outs) {
            body.recycle();
        }
    }
}

    同样看finally块,程序把pageContext的所有变量都置为null,包括session,并把attributes中的数据清空。这里并没有真正的把session给销毁,session是一个会话,不会在一次执行后就销毁它,那是在什么时候销毁的呢?

    原来Tomcat在启动的时候,会启动一个后台线程,这个线程会定时的执行:org.apache.catalina.core.ContainerBase.ContainerBackgroundProcessor,其中就有对session过期的定时检查:void org.apache.catalina.session.StoreBase.processExpires()。

/**
 * Called by our background reaper thread to check if Sessions
 * saved in our store are subject of being expired. If so expire
 * the Session and remove it from the Store.
 *
 */
public void processExpires() {
    String[] keys = null;

    if(!getState().isAvailable()) {
        return;
    }

    try {
        keys = expiredKeys();
    } catch (IOException e) {
        manager.getContainer().getLogger().error("Error getting keys", e);
        return;
    }
    if (manager.getContainer().getLogger().isDebugEnabled()) {
        manager.getContainer().getLogger().debug(getStoreName()+ ": processExpires check number of " + keys.length + " sessions" );
    }

    long timeNow = System.currentTimeMillis();

    for (int i = 0; i < keys.length; i++) {
        try {
            StandardSession session = (StandardSession) load(keys[i]);
            if (session == null) {
                continue;
            }
            int timeIdle = (int) ((timeNow - session.getThisAccessedTime()) / 1000L);
            if (timeIdle < session.getMaxInactiveInterval()) {
                continue;
            }
            if (manager.getContainer().getLogger().isDebugEnabled()) {
                manager.getContainer().getLogger().debug(getStoreName()+ ": processExpires expire store session " + keys[i] );
            }
            boolean isLoaded = false;
            if (manager instanceof PersistentManagerBase) {
                isLoaded = ((PersistentManagerBase) manager).isLoaded(keys[i]);
            } else {
                try {
                    if (manager.findSession(keys[i]) != null) {
                        isLoaded = true;
                    }
                } catch (IOException ioe) {
                    // Ignore - session will be expired
                }
            }
            if (isLoaded) {
                // recycle old backup session
                session.recycle();
            } else {
                // expire swapped out session
                session.expire();
            }
            remove(keys[i]);
        } catch (Exception e) {
            manager.getContainer().getLogger().error("Session: "+keys[i]+"; ", e);
            try {
                remove(keys[i]);
            } catch (IOException e2) {
                manager.getContainer().getLogger().error("Error removing key", e2);
            }
        }
    }
}

首选查询将要过期的sessionkeys,获取当前时间,遍历keys,如果session有效,则检查是否过期,用当前时间减去最后一次访问时间,如果大于最大的不活跃间隔时间段,则认为是过期的。如果session已经过期,判断当前的session池管理器是哪一种(标准管理器StandardManager、持久化管理器PersistentManagerBase、分布式管理器ClusterManagerBase),如果是持久化管理器,就把该session清空回收,这里调用的session.recycle();否则,调用session.expire(),设置session过期,从而从内存中清除出去(详细过程检查上一篇文章:Session的显式创建和销毁流程),最后将该sessionkey从keys数组中remove掉。

 

总结:

    以上就是session的隐式创建和销毁流程,希望对读者有所帮助。