更新時(shí)間:2022-01-25 12:08:06 來源:動(dòng)力節(jié)點(diǎn) 瀏覽1457次
Session對象的創(chuàng)建一般是源于這樣的一條語句:
Session session = request.getSession(false);或者Session session = request.getSession();如果不在乎服務(wù)器壓力可能多那么一點(diǎn)點(diǎn)的話。
在Tomcat的實(shí)現(xiàn)中,這個(gè)request是org.apache.catalina.connector.Request類的包裝類org.apache.catalina.connector.RequestFacade的對象,它的兩個(gè)#getSession()方法如下:
public HttpSession getSession(boolean create) {
if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}
if (SecurityUtil.isPackageProtectionEnabled()){
return (HttpSession)AccessController.
doPrivileged(new GetSessionPrivilegedAction(create));
} else {
return request.getSession(create);
}
}
public HttpSession getSession() {
if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}
return getSession(true);
}
其實(shí)差不太多,最后都會進(jìn)入org.apache.catalina.connector.Request的#getSession()方法。這個(gè)方法的源代碼如下:
public HttpSession getSession(boolean create) {
Session session = doGetSession(create);
if (session != null) {
return session.getSession();
} else {
return null;
}
}
然后調(diào)用就到了#doGetSession()這個(gè)方法了。源代碼如下
protected Session doGetSession(boolean create) {
// 沒有Context的話直接返回null
if (context == null)
return (null);
// 判斷Session是否有效
if ((session != null) && !session.isValid())
session = null;
if (session != null)
return (session);
// 返回Manager對象,這里是StandardManager類的對象
Manager manager = null;
if (context != null)
manager = context.getManager();
if (manager == null)
return (null); // Sessions are not supported
// 判斷是否有SessionID
if (requestedSessionId != null) {
try {
// 在Manager中根據(jù)SessionID查找Session
session = manager.findSession(requestedSessionId);
} catch (IOException e) {
session = null;
}
if ((session != null) && !session.isValid())
session = null;
if (session != null) {
// 更新訪問時(shí)間
session.access();
return (session);
}
}
// 創(chuàng)建新的Session
if (!create)
return (null);
if ((context != null) && (response != null) && context.getCookies()
&& response.getResponse().isCommitted()) {
throw new IllegalStateException(sm.getString("coyoteRequest.sessionCreateCommitted"));
}
// 判斷是否使用 "/" 作為Session Cookie的存儲路徑 并且 是否SessionID來自Cookie
if (connector.getEmptySessionPath() && isRequestedSessionIdFromCookie()) {
// 創(chuàng)建Session
session = manager.createSession(getRequestedSessionId());
} else {
session = manager.createSession(null);
}
// 創(chuàng)建一個(gè)新的Session Cookies
if ((session != null) && (getContext() != null) && getContext().getCookies()) {
Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME, session.getIdInternal());
// 配置Session Cookie
configureSessionCookie(cookie);
// 在響應(yīng)中加入Session Cookie
response.addCookieInternal(cookie);
}
if (session != null) {
// 更新訪問時(shí)間
session.access();
return (session);
} else {
return (null);
}
}
這個(gè)方法說明了Session創(chuàng)建的大致過程,首先判斷requestedSessionId是否存在,如果存在,那么根據(jù)這個(gè)ID去查找Session對象。如果requestedSessionId不存在或者沒有取到Session,并且傳遞給#getSession(boolean)的參數(shù)為真,那么要?jiǎng)?chuàng)建一個(gè)新的Session,并且給客戶端寫回去一個(gè)Session Cookie。
首先,我感興趣的是requestedSessionId的賦值,它到底是什么時(shí)候被賦值的呢?
還要向回看Tomcat的請求處理過程,請求曾到過這一步,org.apache.catalina.connector.CoyoteAdapter的#service()方法。里邊有這樣一句方法調(diào)用:postParseRequest(req, request, res, response)。就是這一步處理了SessionID的獲取,這個(gè)方法調(diào)用了#parseSessionId()和parseSessionCookiesId()這兩個(gè)方法,就是它對Session ID進(jìn)行了提取,源代碼分別如下:
protected void parseSessionId(org.apache.coyote.Request req, Request request) {
ByteChunk uriBC = req.requestURI().getByteChunk();
// 判斷URL中是不是有";jsessionid="這個(gè)字符串
int semicolon = uriBC.indexOf(match, 0, match.length(), 0);
if (semicolon > 0) {
// Parse session ID, and extract it from the decoded request URI
// 在URL中提取Session ID
int start = uriBC.getStart();
int end = uriBC.getEnd();
int sessionIdStart = semicolon + match.length();
int semicolon2 = uriBC.indexOf(';', sessionIdStart);
if (semicolon2 >= 0) {
request.setRequestedSessionId(new String(uriBC.getBuffer(), start + sessionIdStart,
semicolon2 - sessionIdStart));
byte[] buf = uriBC.getBuffer();
for (int i = 0; i < end - start - semicolon2; i++) {
buf[start + semicolon + i] = buf[start + i + semicolon2];
}
uriBC.setBytes(buf, start, end - start - semicolon2 + semicolon);
} else {
request.setRequestedSessionId(new String(uriBC.getBuffer(), start + sessionIdStart,
(end - start) - sessionIdStart));
uriBC.setEnd(start + semicolon);
}
// 設(shè)定Session ID來自于URL
request.setRequestedSessionURL(true);
} else {
request.setRequestedSessionId(null);
request.setRequestedSessionURL(false);
}
}
protected void parseSessionCookiesId(org.apache.coyote.Request req, Request request) {
Context context = (Context) request.getMappingData().context;
if (context != null && !context.getCookies())
return;
// 返回Cookie
Cookies serverCookies = req.getCookies();
int count = serverCookies.getCookieCount();
if (count <= 0)
return;
for (int i = 0; i < count; i++) {
ServerCookie scookie = serverCookies.getCookie(i);
// 判斷是否有JSESSIONID這個(gè)名字的Cookie
if (scookie.getName().equals(Globals.SESSION_COOKIE_NAME)) {
// Override anything requested in the URL
if (!request.isRequestedSessionIdFromCookie()) {
// 設(shè)定Session ID
convertMB(scookie.getValue());
request.setRequestedSessionId(scookie.getValue().toString());
// 如果之前在URL中讀到了SessionID,那么會覆蓋它
request.setRequestedSessionCookie(true);
request.setRequestedSessionURL(false);
if (log.isDebugEnabled())
log.debug(" Requested cookie session id is " + request.getRequestedSessionId());
} else {
if (!request.isRequestedSessionIdValid()) {
convertMB(scookie.getValue());
request.setRequestedSessionId(scookie.getValue().toString());
}
}
}
}
}
Tomcat就是通過上邊的兩個(gè)方法讀到URL或者Cookie中存放的Session ID的。
了解了Session ID的獲取,下面要看一下Session的查找過程,就是org.apache.catalina.session.StandardManager的#findSession()方法。這個(gè)方法是在它的基類中定義的,源代碼如下:
public Session findSession(String id) throws IOException {
if (id == null)
return (null);
return (Session) sessions.get(id);
}
代碼很短,其中sessions是一個(gè)ConcurrentHashMap<String, Session>對象。那么這個(gè)sessions的對象是什么時(shí)候載入的Session呢?
啟動(dòng)的時(shí)候!可以看一下StandardManager#start()方法。最后調(diào)用了#load()方法,這個(gè)就是載入Session的方法了:
public void load() throws ClassNotFoundException, IOException {
if (SecurityUtil.isPackageProtectionEnabled()) {
try {
AccessController.doPrivileged(new PrivilegedDoLoad());
} catch (PrivilegedActionException ex) {
Exception exception = ex.getException();
if (exception instanceof ClassNotFoundException) {
throw (ClassNotFoundException) exception;
} else if (exception instanceof IOException) {
throw (IOException) exception;
}
if (log.isDebugEnabled())
log.debug("Unreported exception in load() " + exception);
}
} else {
doLoad();
}
}
最后調(diào)用了#doLoad()方法來具體的載入Session,源代碼如下:
protected void doLoad() throws ClassNotFoundException, IOException {
if (log.isDebugEnabled())
log.debug("Start: Loading persisted sessions");
// 清空Map
sessions.clear();
// 對應(yīng)work/Catalina/localhost/%app name%/SESSIONS.ser文件
File file = file();
if (file == null)
return;
if (log.isDebugEnabled())
log.debug(sm.getString("standardManager.loading", pathname));
FileInputStream fis = null;
ObjectInputStream ois = null;
Loader loader = null;
ClassLoader classLoader = null;
try {
// 載入Session緩存文件
fis = new FileInputStream(file.getAbsolutePath());
BufferedInputStream bis = new BufferedInputStream(fis);
if (container != null)
loader = container.getLoader();
if (loader != null)
classLoader = loader.getClassLoader();
if (classLoader != null) {
if (log.isDebugEnabled())
log.debug("Creating custom object input stream for class loader ");
ois = new CustomObjectInputStream(bis, classLoader);
} else {
if (log.isDebugEnabled())
log.debug("Creating standard object input stream");
ois = new ObjectInputStream(bis);
}
} catch (FileNotFoundException e) {
if (log.isDebugEnabled())
log.debug("No persisted data file found");
return;
} catch (IOException e) {
log.error(sm.getString("standardManager.loading.ioe", e), e);
if (ois != null) {
try {
ois.close();
} catch (IOException f) {
;
}
ois = null;
}
throw e;
}
synchronized (sessions) {
try {
// 讀出Session個(gè)數(shù)
Integer count = (Integer) ois.readObject();
int n = count.intValue();
if (log.isDebugEnabled())
log.debug("Loading " + n + " persisted sessions");
// 讀入Session
for (int i = 0; i < n; i++) {
StandardSession session = getNewSession();
session.readObjectData(ois);
session.setManager(this);
sessions.put(session.getIdInternal(), session);
session.activate();
sessionCounter++;
}
} catch (ClassNotFoundException e) {
log.error(sm.getString("standardManager.loading.cnfe", e), e);
if (ois != null) {
try {
ois.close();
} catch (IOException f) {
;
}
ois = null;
}
throw e;
} catch (IOException e) {
log.error(sm.getString("standardManager.loading.ioe", e), e);
if (ois != null) {
try {
ois.close();
} catch (IOException f) {
;
}
ois = null;
}
throw e;
} finally {
try {
if (ois != null)
ois.close();
} catch (IOException f) {
}
// 刪除Session緩存文件
if (file != null && file.exists())
file.delete();
}
}
if (log.isDebugEnabled())
log.debug("Finish: Loading persisted sessions");
}
大致知道了Session的讀取過程,后面就是Session沒找到時(shí)創(chuàng)建Session的過程了。具體就是org.apache.catalina.session.StandardManager的#createSession()方法:
public Session createSession(String sessionId) {
if ((maxActiveSessions >= 0) && (sessions.size() >= maxActiveSessions)) {
rejectedSessions++;
throw new IllegalStateException(sm.getString("standardManager.createSession.ise"));
}
return (super.createSession(sessionId));
}
最后調(diào)用到了它的基類的#createSession()方法了。
public Session createSession(String sessionId) {
// 創(chuàng)建一個(gè)新的Session
Session session = createEmptySession();
// 初始化Session的屬性
session.setNew(true);
session.setValid(true);
session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval(this.maxInactiveInterval);
// 如果Session ID為null,那么就生成一個(gè)
if (sessionId == null) {
sessionId = generateSessionId();
}
session.setId(sessionId);
sessionCounter++;
return (session);
}
通過上述過程,一個(gè)新的Session就創(chuàng)建出來了。
初級 202925
初級 203221
初級 202629
初級 203743