一、Session的使用
在servlet中取得Session:
HttpSession session = request.getSession();
request.getSession()即org.apache.catalina.connector.Request.getSession():
public HttpSession getSession() {
Session session = doGetSession(true);
if (session != null) {
return session.getSession();
} else {
return null;
}
}
1.doGetSession():
protected Session doGetSession(boolean create) {
// There cannot be a session if no context has been assigned yet
if (context == null)
return (null);
// Return the current session if it exists and is valid
if ((session != null) && !session.isValid())
session = null;
if (session != null)
return (session);
// Return the requested session if it exists and is valid
Manager manager = null;
if (context != null)
manager = context.getManager();
if (manager == null)
return (null); // Sessions are not supported
if (requestedSessionId != null) {
try {
session = manager.findSession(requestedSessionId);
} catch (IOException e) {
session = null;
}
if ((session != null) && !session.isValid())
session = null;
if (session != null) {
session.access();
return (session);
}
}
// Create a new session if requested and the response is not committed
if (!create)
return (null);
if ((context != null) && (response != null) &&
context.getCookies() &&
response.getResponse().isCommitted()) {
throw new IllegalStateException
(sm.getString("coyoteRequest.sessionCreateCommitted"));
}
// Attempt to reuse session id if one was submitted in a cookie
// Do not reuse the session id if it is from a URL, to prevent possible
// phishing attacks
if (connector.getEmptySessionPath()
&& isRequestedSessionIdFromCookie()) {
session = manager.createSession(getRequestedSessionId());
} else {
session = manager.createSession(null);
}
// Creating a new session cookie based on that session
if ((session != null) && (getContext() != null)
&& getContext().getCookies()) {
String scName = context.getSessionCookieName();
if (scName == null) {
scName = Globals.SESSION_COOKIE_NAME;
}
Cookie cookie = new Cookie(scName, session.getIdInternal());
configureSessionCookie(cookie);
response.addSessionCookieInternal(cookie, context.getUseHttpOnly());
}
if (session != null) {
session.access();
return (session);
} else {
return (null);
}
}
如果有session就查出,没有的话就创建一个,并且将ID保存到cookie中。
context.getManager();返回的是StandardManager对象,session就保存在其中。StandardManager对象是在StandardContext.start()时创建的:
Manager contextManager = null;
if (manager == null) {
if ( (getCluster() != null) && distributable) {
try {
contextManager = getCluster().createManager(getName());
} catch (Exception ex) {
log.error("standardContext.clusterFail", ex);
ok = false;
}
} else {
contextManager = new StandardManager();
}
}
// Configure default manager if none was specified
if (contextManager != null) {
setManager(contextManager);
}
StandardManager保存Session,还将处理过期的Session。
(1).第一次调用getSession(),requestedSessionId为null,就会去创建一个Session:
session = manager.createSession(null);
StandardManager.createSession():
public Session createSession(String sessionId) {
if ((maxActiveSessions >= 0) &&
(sessions.size() >= maxActiveSessions)) {
rejectedSessions++;
throw new TooManyActiveSessionsException(
sm.getString("standardManager.createSession.ise"),
maxActiveSessions);
}
return (super.createSession(sessionId));
}
调用父类ManagerBase.createSession():
public Session createSession(String sessionId) {
// Recycle or create a Session instance
Session session = createEmptySession();
// Initialize the properties of the new session and return it
session.setNew(true);
session.setValid(true);
session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval(this.maxInactiveInterval);
if (sessionId == null) {
sessionId = generateSessionId();
}
session.setId(sessionId);
sessionCounter++;
SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
synchronized (sessionCreationTiming) {
sessionCreationTiming.add(timing);
sessionCreationTiming.poll();
}
return (session);
}
createEmptySession(),调用getNewSession():
protected StandardSession getNewSession() {
return new StandardSession(this);
}
sessionId来自ManagerBasic.generateSessionId:
protected synchronized String generateSessionId() {
byte random[] = new byte[16];
String jvmRoute = getJvmRoute();
String result = null;
// Render the result as a String of hexadecimal digits
StringBuffer buffer = new StringBuffer();
do {
int resultLenBytes = 0;
if (result != null) {
buffer = new StringBuffer();
duplicates++;
}
while (resultLenBytes < this.sessionIdLength) {
getRandomBytes(random);
random = getDigest().digest(random);
for (int j = 0;
j < random.length && resultLenBytes < this.sessionIdLength;
j++) {
byte b1 = (byte) ((random[j] & 0xf0) >> 4);
byte b2 = (byte) (random[j] & 0x0f);
if (b1 < 10)
buffer.append((char) ('0' + b1));
else
buffer.append((char) ('A' + (b1 - 10)));
if (b2 < 10)
buffer.append((char) ('0' + b2));
else
buffer.append((char) ('A' + (b2 - 10)));
resultLenBytes++;
}
}
if (jvmRoute != null) {
buffer.append('.').append(jvmRoute);
}
result = buffer.toString();
} while (sessions.containsKey(result));
return (result);
}
session.setId()会调用重构方法,即StandardSession.setId():
public void setId(String id, boolean notify) {
if ((this.id != null) && (manager != null))
manager.remove(this);
this.id = id;
if (manager != null)
manager.add(this);
if (notify) {
tellNew();
}
}
manager.add()即StandardManager父类ManagerBase.add(),将session添加到session添加到ConcurrentHashMap<String, Session>对象sessions中。
(2).设置session后,第二个请求报头中就会多出一行:
Cookie: JSESSIONID=*******
第二次进行request.getSession()就不用去创建,会根据这个JSESSIONID去查找:
session = manager.findSession(requestedSessionId);
requestedSessionId来自CoyoteAdapter.parseSessionCookiesId():
protected void parseSessionCookiesId(org.apache.coyote.Request req, Request request) {
// If session tracking via cookies has been disabled for the current
// context, don't go looking for a session ID in a cookie as a cookie
// from a parent context with a session ID may be present which would
// overwrite the valid session ID encoded in the URL
Context context = (Context) request.getMappingData().context;
if (context != null && !context.getCookies())
return;
// Parse session id from cookies
Cookies serverCookies = req.getCookies();
int count = serverCookies.getCookieCount();
if (count <= 0)
return;
String sessionCookieName = getSessionCookieName(context);
for (int i = 0; i < count; i++) {
ServerCookie scookie = serverCookies.getCookie(i);
if (scookie.getName().equals(sessionCookieName)) {
// Override anything requested in the URL
if (!request.isRequestedSessionIdFromCookie()) {
// Accept only the first session id cookie
convertMB(scookie.getValue());
request.setRequestedSessionId
(scookie.getValue().toString());
request.setRequestedSessionCookie(true);
request.setRequestedSessionURL(false);
if (log.isDebugEnabled())
log.debug(" Requested cookie session id is " +
request.getRequestedSessionId());
} else {
if (!request.isRequestedSessionIdValid()) {
// Replace the session id until one is valid
convertMB(scookie.getValue());
request.setRequestedSessionId
(scookie.getValue().toString());
}
}
}
}
}
Cookies serverCookies = req.getCookies();调用的是org.apache.coyote.Request.getCookies()返回的是Cookies属性:
private Cookies cookies = new Cookies(headers);
int count = serverCookies.getCookieCount();调用的是Cookies.getCookieCount():
public int getCookieCount() {
if( unprocessed ) {
unprocessed=false;
processCookies(headers);
}
return cookieCount;
}
headers是org.apache.coyote.Request的属性,在Http11Processer.process()中调用inputBuffer.parseHeaders()时往里边add值。
Cookies.processCookies(headers): public void processCookies( MimeHeaders headers ) {
if( headers==null )
return;// nothing to process
// process each "cookie" header
int pos=0;
while( pos>=0 ) {
// Cookie2: version ? not needed
pos=headers.findHeader( "Cookie", pos );
// no more cookie headers headers
if( pos<0 ) break;
MessageBytes cookieValue=headers.getValue( pos );
if( cookieValue==null || cookieValue.isNull() ) {
pos++;
continue;
}
// Uncomment to test the new parsing code
if( cookieValue.getType() == MessageBytes.T_BYTES ) {
if( dbg>0 ) log( "Parsing b[]: " + cookieValue.toString());
ByteChunk bc=cookieValue.getByteChunk();
processCookieHeader( bc.getBytes(),
bc.getOffset(),
bc.getLength());
} else {
if( dbg>0 ) log( "Parsing S: " + cookieValue.toString());
processCookieHeader( cookieValue.toString() );
}
pos++;// search from the next position
}
}
pocessCookieHeader()才开始去解析请求报头中的cookie,用到时才解析的处理方法和parameter相似。
(3).将sessionId保存到cookie中:
Cookie cookie = new Cookie(scName, session.getIdInternal());
configureSessionCookie(cookie);
response.addSessionCookieInternal(cookie, context.getUseHttpOnly())
cookie保存的是键值对,保存在浏览器内存中,跨窗口无法访问,窗口关掉即消失。和正常所说的Cookie不同的是,后者保存在硬盘上。
2.回到org.apache.catalina.connector.Request.getSession():
return session.getSession();
即StandardSession.getSession():
public HttpSession getSession() {
if (facade == null){
if (SecurityUtil.isPackageProtectionEnabled()){
final StandardSession fsession = this;
facade = (StandardSessionFacade)AccessController.doPrivileged(new PrivilegedAction(){
public Object run(){
return new StandardSessionFacade(fsession);
}
});
} else {
facade = new StandardSessionFacade(this);
}
}
return (facade);
}
facade = new StandardSessionFacade(this);StandardSessionFacade是的对StandardSession的封装,屏蔽了些方法。
那么request.getSession()返回的是StandardSessionFacade对象。
还有另外一个方法getSession(boolean create):
public HttpSession getSession(boolean create) {
Session session = doGetSession(create);
if (session != null) {
return session.getSession();
} else {
return null;
}
}
也是调用doGetSession();那么getSession()=getSession(true);而getSession(false)调用doGetSession(false),回去查找session是否存
二、Session过期处理:
StandardContext父类ContainerBase.threadStart():
protected void threadStart() {
if (thread != null)
return;
if (backgroundProcessorDelay <= 0)
return;
threadDone = false;
String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
thread = new Thread(new ContainerBackgroundProcessor(), threadName);
thread.setDaemon(true);
thread.start();
}
启动内部类线程ContainerBackgroundProcessor.run():
public void run() {
while (!threadDone) {
try {
Thread.sleep(backgroundProcessorDelay * 1000L);
} catch (InterruptedException e) {
;
}
if (!threadDone) {
Container parent = (Container) getMappingObject();
ClassLoader cl =
Thread.currentThread().getContextClassLoader();
if (parent.getLoader() != null) {
cl = parent.getLoader().getClassLoader();
}
processChildren(parent, cl);
}
}
}
getMappingObject()方法返回this,即StandardContext对象。ContainerBase$ContainerBackgroundProcessor.processChildren():
protected void processChildren(Container container, ClassLoader cl) {
try {
if (container.getLoader() != null) {
Thread.currentThread().setContextClassLoader
(container.getLoader().getClassLoader());
}
container.backgroundProcess();
} catch (Throwable t) {
log.error("Exception invoking periodic operation: ", t);
} finally {
Thread.currentThread().setContextClassLoader(cl);
}
Container[] children = container.findChildren();
for (int i = 0; i < children.length; i++) {
if (children[i].getBackgroundProcessorDelay() <= 0) {
processChildren(children[i], cl);
}
}
}
container.backgroundProcess()调用StandardContext父类ContainerBase.backgroundProcess():
public void backgroundProcess() {
if (!started)
return;
if (cluster != null) {
try {
cluster.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.cluster", cluster), e);
}
}
if (loader != null) {
try {
loader.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.loader", loader), e);
}
}
if (manager != null) {
try {
manager.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.manager", manager), e);
}
}
if (realm != null) {
try {
realm.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.realm", realm), e);
}
}
Valve current = pipeline.getFirst();
while (current != null) {
try {
current.backgroundProcess();
} catch (Exception e) {
log.warn(sm.getString("containerBase.backgroundProcess.valve", current), e);
}
current = current.getNext();
}
lifecycle.fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
}
manager即StandardManager,在StandardContext.start()时创建的。manager.backgroundProcess()调用StandardManager父类ManagerBase.backgroundProcess():
public void backgroundProcess() {
count = (count + 1) % processExpiresFrequency;
if (count == 0)
processExpires();
}
ManagerBase.processExpires():
public void processExpires() {
long timeNow = System.currentTimeMillis();
Session sessions[] = findSessions();
int expireHere = 0 ;
if(log.isDebugEnabled())
log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
for (int i = 0; i < sessions.length; i++) {
if (sessions[i]!=null && !sessions[i].isValid()) {
expireHere++;
}
}
long timeEnd = System.currentTimeMillis();
if(log.isDebugEnabled())
log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere);
processingTime += ( timeEnd - timeNow );
}
StandardSession.isValid()判断session最后操作时间到现在的时间差是否超出最大时间:
public boolean isValid() {
if (this.expiring) {
return true;
}
if (!this.isValid) {
return false;
}
if (ACTIVITY_CHECK && accessCount.get() > 0) {
return true;
}
if (maxInactiveInterval >= 0) {
long timeNow = System.currentTimeMillis();
int timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);
if (timeIdle >= maxInactiveInterval) {
expire(true);
}
}
return (this.isValid);
}
thisAccessedTime:最后一次访问session的时间,在org.apache.catalina.connector.Request.doGetSession()中调用StandardSession.access()时设置当前时间。
maxInactiveInterval:ManagerBase.createSession(String sessionId)中创建session的时候就设置有效期:
session.setMaxInactiveInterval(this.maxInactiveInterval);
StandardSession.setMaxInactiveInterval():
public void setMaxInactiveInterval(int interval) {
this.maxInactiveInterval = interval;
if (isValid && interval == 0) {
expire();
}
}
那么ManagerBase的maxInactiveInterval属性是什么时候设置的?回头看看在StandardContext.start()中创建StandardManager的时候有:
if (contextManager != null) {
setManager(contextManager);
}
StandardContext的父类ContainerBase.setManager():
public synchronized void setManager(Manager manager) {
// Change components if necessary
Manager oldManager = this.manager;
if (oldManager == manager)
return;
this.manager = manager;
// Stop the old component if necessary
if (started && (oldManager != null) &&
(oldManager instanceof Lifecycle)) {
try {
((Lifecycle) oldManager).stop();
} catch (LifecycleException e) {
log.error("ContainerBase.setManager: stop: ", e);
}
}
// Start the new component if necessary
if (manager != null)
manager.setContainer(this);
if (started && (manager != null) &&
(manager instanceof Lifecycle)) {
try {
((Lifecycle) manager).start();
} catch (LifecycleException e) {
log.error("ContainerBase.setManager: start: ", e);
}
}
// Report this property change to interested listeners
support.firePropertyChange("manager", oldManager, this.manager);
}
manager.setContainer(this);就是将StandardManager和StandardContext关联起来,StandardManager.setContainer():
public void setContainer(Container container) {
// De-register from the old Container (if any)
if ((this.container != null) && (this.container instanceof Context))
((Context) this.container).removePropertyChangeListener(this);
// Default processing provided by our superclass
super.setContainer(container);
// Register with the new Container (if any)
if ((this.container != null) && (this.container instanceof Context)) {
setMaxInactiveInterval
( ((Context) this.container).getSessionTimeout()*60 );
((Context) this.container).addPropertyChangeListener(this);
}
}
digester.addCallMethod(prefix + "web-app/session-config/session-timeout",
"setSessionTimeout", 1,
new Class[] { Integer.TYPE });
digester.addCallParam(prefix + "web-app/session-config/session-timeout", 0);
StandardContext.setSessionTimeout():
public void setSessionTimeout(int timeout) {
int oldSessionTimeout = this.sessionTimeout;
/*
* SRV.13.4 ("Deployment Descriptor"):
* If the timeout is 0 or less, the container ensures the default
* behaviour of sessions is never to time out.
*/
this.sessionTimeout = (timeout == 0) ? -1 : timeout;
support.firePropertyChange("sessionTimeout",
oldSessionTimeout,
this.sessionTimeout);
}
这个maxInactiveInterval的来源:web.xml配置session-timeout(单位是分钟)——StandardContext.setSessionTimeout()——StandardManager.setMaxInactiveInterval()(单位是秒)——StandardSession.setMaxInactiveInterval()——StandardSession.inValid()。