Session的隐式创建和销毁流程
上文通过源码,分析了session显式创建和销毁的流程,但有些时候,我们并没有亲自去创建session,但不代表它不存在,笔者将这种情况,称之为隐式创建和销毁。
一、创建流程
这里介绍session隐式创建的一种情况,即jsp的执行过程。由于jsp内置9个对象,其中就有session,在不禁用session的情况下(<%@page session="false"%>可以禁止创建session),就会创建session。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在第一次运行的时候,会由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; }
这里面获取了一系列的对象,放到了pageContext的attribte中,其中就包含session。这里创建session时也是调取了request.getSession(),具体细节可以查看上一篇文章。
二、销毁流程
上面的代码可以看到,在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的隐式创建和销毁流程,希望对读者有所帮助。