Tomcat
中对于Session
相关的框架和查询Session
Servlet
中, Session
代表着用户会话, 开发人员经常用Session
存储一些数据
Session
是什么Tomcat
如何对Session
进行管理的。
两个接口: Session
和 HttpSession
, StandardSession
实现了这两个接口, StandardSessionFacade
实现了 HttpSession,
StandardSessionFacade
包含了 StandardSession
。
HttpSession
HttpSession
就是在Servlet
规范中标准的Session
定义。注意HttpSession
的全路径是javax.servlet.http.HttpSession
Session
Session
接口是Catalina
内部的Session
的标准定义。全路径org.apache.catalina.Session
StandardSession
Catalina
内部Session
接口的标准实现,基础功能的提供类,需要重点分析,全路径org.apache.catalina.session.StandardSession
StandardSessionFacade
StandardSession
的外观类,也是Catalina
内部的类,之所以存在这个类是因为不想直接让开发人员操作到StandardSession
,这样可以保证类的安全。该类的全路径org.apache.catalina.session.StandardSession
如何管理Session
的, 比如创建和销毁, org.apache.catalina.Manager
Manager
Catalina
中session
管理器概念的顶层接口
。其中定义了一些对session
的基本操作
,如创建,查找,添加,移除以及对特定session对象的属性的修改。除了基本操作以外还定义了一些特殊的例如session的序列化反序列化
等等。
ManagerBase
抽象类,对Manager
接口作了基本实现,方便继承类的复写以及实现,就像其他xxxBase
类起到了一样的作用。
StandardManager
Catalina
中默认的Session
管理器的实现类
PersistentManagerBase
Session
管理器中打标持久化Session
的父类, 虽然StandardManager
也可以将Session
持久化,但是只是将Session
持久化为一个 文件
, PersistentManagerBase
类和StandardManager
类的区别在于前者的存储器的表现形式可以有多种,比如数据库,文件
等。
PersistentManager
在PersistentManagerBase
基础上增加了两个属性。
DistributedManager
从PersistentManagerBase
和BackupManager
类中抽象出来的一个接口,这个接口表示了两者一个共同的属性:不会持有所有session
的对象,但是能找到所有的session
。例如PersistentManagerBase
可以将session
同时存放在内存
和持久化介质
中。
ClusterManager
分布式集群session
处理器父类,定义了一些基本方法例如获取所有tomcat
集群的机器,获取其他集群的信息等基本功能。
ClusterManagerBase
抽象类,对ClusterManager
作了基本实现。
BackupManager
集群间session复制策略
的一种实现,会话数据只有一个备份节点
,这个备份节点的位置集群中所有节点都可见。
DeltaManager
集群建session复制策略的一种实现
,采用的方式是只复制差异部分
,是分布式集群session同步
中最好的同步方式。
在Servlet
中我们使用HttpServletRequest
的getSession()
方法来获取session对象
,而真正执行getSession
方法的其实是org.apache.catalina.connector.RequestFacade
对象
RequestFacade
对象实现了HttpServletRequest
内部封装了org.apache.catalina.connector.Request
对象
@Override
public HttpSession getSession() {
if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}
return getSession(true);
}
@Override
public HttpSession getSession(boolean create) {
if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}
if (SecurityUtil.isPackageProtectionEnabled()){
return AccessController.
doPrivileged(new GetSessionPrivilegedAction(create));
} else {
return request.getSession(create);
}
}
最终调用的还是org.apache.catalina.connector.Request
对象的getSession()
方法
@Override
public HttpSession getSession(boolean create) {
Session session = doGetSession(create);
if (session == null) {
return null;
}
return session.getSession();
}
doGetSession()
protected Session doGetSession(boolean create) {
// 判断Context
Context context = getContext();
if (context == null) {
return null;
}
// 判断Session
if ((session != null) && !session.isValid()) {
session = null;
}
if (session != null) {
return session;
}
// 获取跟context绑定的sessionManager 这里默认返回的是StandardManager
Manager manager = context.getManager();
if (manager == null) {
return null; // Sessions are not supported
}
if (requestedSessionId != null) {
try {
// 根据requestSessionId 查询指定的session
session = manager.findSession(requestedSessionId);
} catch (IOException e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("request.session.failed", requestedSessionId, e.getMessage()), e);
} else {
log.info(sm.getString("request.session.failed", requestedSessionId, e.getMessage()));
}
session = null;
}
// 判断获取到的session是否有效
if ((session != null) && !session.isValid()) {
session = null;
}
if (session != null) {
// session有效 增加访问时间和次数
session.access();
return session;
}
}
// Create a new session if requested and the response is not committed
if (!create) {
return null;
}
boolean trackModesIncludesCookie =
context.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.COOKIE);
if (trackModesIncludesCookie && response.getResponse().isCommitted()) {
throw new IllegalStateException(sm.getString("coyoteRequest.sessionCreateCommitted"));
}
// 如果没有找到已存在的session并且 要求创建新session
// 获取此次request对应的sessionid
String sessionId = getRequestedSessionId();
...
session = manager.createSession(sessionId);
// 创建cookie
if (session != null && trackModesIncludesCookie) {
Cookie cookie = ApplicationSessionCookieConfig.createSessionCookie(
context, session.getIdInternal(), isSecure());
// cookie设置到session中
response.addSessionCookieInternal(cookie);
}
if (session == null) {
return null;
}
// 增加访问时间和次数
session.access();
return session;
}
manager.findSession()
@Override
public Session findSession(String id) throws IOException {
if (id == null) {
return null;
}
return sessions.get(id);
}
protected Map<String, Session> sessions = new ConcurrentHashMap<>();
一个类型的session
管理器,他都有一个总的session容器
,就是一个ConcurrentHashMap
实例,key是sessionId
,value是对应的session对象
。
session
的map中没有找到session
,所以需要创建一个新的session
manager.createSession()
@Override
public Session createSession(String sessionId) {
//判断可容纳session数量
if ((maxActiveSessions >= 0) &&
(getActiveSessions() >= maxActiveSessions)) {
rejectedSessions++;
throw new TooManyActiveSessionsException(
sm.getString("managerBase.createSession.ise"),
maxActiveSessions);
}
//创建一个全新的session对象
Session session = createEmptySession();
//设置基本属性
session.setNew(true);
session.setValid(true);
session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval(((Context) getContainer()).getSessionTimeout() * 60);
String id = sessionId;
//设置sessionId 唯一标识负
if (id == null) {
//如果id为空 新生成一个sessionId
id = generateSessionId();
}
session.setId(id);
//session数量+1
sessionCounter++;
SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
synchronized (sessionCreationTiming) {
sessionCreationTiming.add(timing);
sessionCreationTiming.poll();
}
return (session);
}
创建新的session
对象其实就是new 了一个StandardSession
对象,还有generateSessionId()
方法是生成一个唯一的sessionId
session.setId(id);
也就是把新创建的session
放入到管理器的session
容器中(ConcurrentHashMap)
对象。
在doGetSession
方法中最后需要关注的就是requestedSessionId
是如何生成的
/**
* The requested session ID (if any) for this request.
*/
protected String requestedSessionId = null;
和request
有关, requestedSessionId
对应的就是我们每次请求都有一个唯一session标识符
, 为了以后同一个用户再次请求的时候可以使用同一个session
用户发送一个http请求传递给Http11Processor
,经由Http11Processor
解析封装在 org.apache.coyote.Request
然后传递给CoyoteAdapter
,coyoteAdapter
是一个适配器,将coyote
框 架封装的org.apache.coyote.Request
适配给org.apache.catalina.connector.Request
,而解析的过程就在CoyoteAdapter
中。
CoyoteAdapter.parsePathParameters()
protected void parsePathParameters(org.apache.coyote.Request req,
Request request) {
//转码uri
req.decodedURI().toBytes();
ByteChunk uriBC = req.decodedURI().getByteChunk();
//查询;位置
int semicolon = uriBC.indexOf(';', 0);
//设置编码
String enc = connector.getURIEncoding();
if (enc == null) {
enc = "ISO-8859-1";
}
Charset charset = null;
try {
charset = B2CConverter.getCharset(enc);
} catch (UnsupportedEncodingException e1) {
log.warn(sm.getString("coyoteAdapter.parsePathParam",
enc));
}
//省略部分代码
//循环遍历,设置所有的kv
while (semicolon > -1) {
// 11111111111111
int start = uriBC.getStart();
int end = uriBC.getEnd();
int pathParamStart = semicolon + 1;
int pathParamEnd = ByteChunk.findBytes(uriBC.getBuffer(),
start + pathParamStart, end,
new byte[] {';', '/'});
String pv = null;
if (pathParamEnd >= 0) {
if (charset != null) {
pv = new String(uriBC.getBuffer(), start + pathParamStart,
pathParamEnd - pathParamStart, charset);
}
// Extract path param from decoded request URI
byte[] buf = uriBC.getBuffer();
for (int i = 0; i < end - start - pathParamEnd; i++) {
buf[start + semicolon + i]
= buf[start + i + pathParamEnd];
}
uriBC.setBytes(buf, start,
end - start - pathParamEnd + semicolon);
} else {
if (charset != null) {
pv = new String(uriBC.getBuffer(), start + pathParamStart,
(end - start) - pathParamStart, charset);
}
uriBC.setEnd(start + semicolon);
}
if (pv != null) {
int equals = pv.indexOf('=');
if (equals > -1) {
String name = pv.substring(0, equals);
String value = pv.substring(equals + 1);
request.addPathParameter(name, value);
}
}
semicolon = uriBC.indexOf(';', semicolon);
}
}
解析url的时候第一次设置requestSessionId
从cookie中解析
protected void parseSessionCookiesId(org.apache.coyote.Request req, Request request) {
//如果cookie被禁用 直接返回
Context context = (Context) request.getMappingData().context;
if (context != null && !context.getServletContext()
.getEffectiveSessionTrackingModes().contains(
SessionTrackingMode.COOKIE)) {
return;
}
//获取cookie
Cookies serverCookies = req.getCookies();
int count = serverCookies.getCookieCount();
if (count <= 0) {
return;
}
//获取cookie中session的key
String sessionCookieName = SessionConfig.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());
//设置requestSessionId
request.setRequestedSessionId(scookie.getValue().toString());
request.setRequestedSessionCookie(true);
request.setRequestedSessionURL(false);
} else {
if (!request.isRequestedSessionIdValid()) {
// Replace the session id until one is valid
convertMB(scookie.getValue());
request.setRequestedSessionId
(scookie.getValue().toString());
}
}
}
}
}
getSession()
方法中返回的是StandardSession
对象的getSession()
方法
@Override
public HttpSession getSession() {
Session session = doGetSession(true);
if (session == null) {
return null;
}
return session.getSession();
}
@Override
public HttpSession getSession() {
if (facade == null){
if (SecurityUtil.isPackageProtectionEnabled()){
final StandardSession fsession = this;
facade = AccessController.doPrivileged(
new PrivilegedAction<StandardSessionFacade>(){
@Override
public StandardSessionFacade run(){
return new StandardSessionFacade(fsession);
}
});
} else {
facade = new StandardSessionFacade(this);
}
}
return (facade);
}
可以看出最后getSession()
方法返回的并不是StandardSession
对象,而是StandardSession
对象的外观类StandardSessionFacade
之所以存在这个类是因为不想直接让开发人员操作到StandardSession
,这样可以保证类的安全。