上文通过源码,分析了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;
}
二、销毁流程
上面的代码可以看到,在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的隐式创建和销毁流程,希望对读者有所帮助。