设为首页 收藏本站
查看: 717|回复: 0

[经验分享] How Tomcat Works(十二)

[复制链接]

尚未签到

发表于 2015-8-8 10:36:46 | 显示全部楼层 |阅读模式
  tomcat容器通过一个称为Session管理器的组件来管理建立的Session对象,该组件由org.apache.catalina.Manager接口表示;Session管理器必须与一个Context容器相关联(需要用到Context容器的相关上下文或方法)。
  默认情况下,Session管理器会将其所管理的 Session对象存放在内存中,不过在tomcat中,Session管理器也库将Session对象持久化,存储到文件存储器或通过JDBC写入到数据库中。
  下面我们来分析具体实现,在servlet编程方面中,Session对象由javax.servlet.http.HttpSession接口表示;在tomcat中该接口的标准实现是org.apache.catalina.session包下的StandardSession类,该类同时实现了org.apache.catalina.Session接口,在tomcat内部供Session管理器使用;而实际交给servlet实例使用的是Session接口的外观类StandardSessionFacade
  下面是Session管理器内部使用的Session接口



public interface Session {

public static final String SESSION_CREATED_EVENT = "createSession";

public static final String SESSION_DESTROYED_EVENT = "destroySession";

public String getAuthType();

public void setAuthType(String authType);

public long getCreationTime();

public void setCreationTime(long time);

public String getId();

public void setId(String id);

public String getInfo();

public long getLastAccessedTime();

public Manager getManager();

public void setManager(Manager manager);

public int getMaxInactiveInterval();

public void setMaxInactiveInterval(int interval);

public void setNew(boolean isNew);

public Principal getPrincipal();

public void setPrincipal(Principal principal);

public HttpSession getSession();

public void setValid(boolean isValid);

public boolean isValid();

public void access();

public void addSessionListener(SessionListener listener);

public void expire();

public Object getNote(String name);

public Iterator getNoteNames();

public void recycle();

public void removeNote(String name);

public void removeSessionListener(SessionListener listener);

public void setNote(String name, Object value);
}
  Session对象总是存在于Session管理器中,可以通过setManager()方法将Session实例 与某个Session管理器相关联;Session管理器可以通过setId()方法设置Session标识符;同时会调用getLastAccessedTime()方法判断一个Session对象的有效性;setValid()方法用于重置该Session对象的有效性;每当访问一个Session实例时,会调用access()方法来修改Session对象的最后访问时间;最后,Session管理器会调用Session对象的expire()方法使其过期,通过getSession()方法获取一个经过外观类StandardSessionFacade包装的HttpSession对象
  StandardSession类是Session接口的标准实现,同时实现了javax.servlet.http.HttpSession接口和java.lang.Serializable接口
  (注:StandardSession类实现HttpSession接口方法的实现基本上都依赖于实现Session接口的方法对StandardSession实例的填充,因此我们可以想象,StandardSession类基本上类似与适配器模式中的Adapter角色,实现了原类型(Session接口类型)到目标接口的转换(HttpSession接口))
  其构造函数接受一个Manager接口的实例,迫使Session对象必须拥有一个Session管理器实例



public StandardSession(Manager manager) {
super();
this.manager = manager;
if (manager instanceof ManagerBase)
this.debug = ((ManagerBase) manager).getDebug();
}
  下面是StandardSession实例的一些比较重要的私有成员变量



//存储session的键值对
private HashMap attributes = new HashMap();
private transient String authType = null;
//创建时间
private long creationTime = 0L;
//是否过期
private transient boolean expiring = false;
//外观类
private transient StandardSessionFacade facade = null;
//session标识
private String id = null;
//最后访问时间
private long lastAccessedTime = creationTime;
//监听器聚集
private transient ArrayList listeners = new ArrayList();
//session管理器
private Manager manager = null;
//清理session过期时间
private int maxInactiveInterval = -1;
private boolean isNew = false;
private boolean isValid = false;
//当前访问时间
private long thisAccessedTime = creationTime;
  其中getSession()方法通过传入自身实例来创建外观类StandardSessionFacade实例



public HttpSession getSession() {
if (facade == null)
facade = new StandardSessionFacade(this);
return (facade);
}
  如果session管理器中的某个Session对象在某个时间长度内都没有被访问的话,会被Session管理器设置为过期,这个时间长度是由变量maxInactiveInterval的值来指定



public void expire(boolean notify) {
// Mark this session as "being expired" if needed
if (expiring)
return;
expiring = true;
setValid(false);
// Remove this session from our manager's active sessions
if (manager != null)
manager.remove(this);
// Unbind any objects associated with this session
String keys[] = keys();
for (int i = 0; i < keys.length; i++)
removeAttribute(keys, notify);
// Notify interested session event listeners
if (notify) {
fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);
}
// Notify interested application event listeners
// FIXME - Assumes we call listeners in reverse order
Context context = (Context) manager.getContainer();
Object listeners[] = context.getApplicationListeners();
if (notify && (listeners != null)) {
HttpSessionEvent event =
new HttpSessionEvent(getSession());
for (int i = 0; i < listeners.length; i++) {
int j = (listeners.length - 1) - i;
if (!(listeners[j] instanceof HttpSessionListener))
continue;
HttpSessionListener listener =
(HttpSessionListener) listeners[j];
try {
fireContainerEvent(context,
"beforeSessionDestroyed",
listener);
listener.sessionDestroyed(event);
fireContainerEvent(context,
"afterSessionDestroyed",
listener);
} catch (Throwable t) {
try {
fireContainerEvent(context,
"afterSessionDestroyed",
listener);
} catch (Exception e) {
;
}
// FIXME - should we do anything besides log these?
log(sm.getString("standardSession.sessionEvent"), t);
}
}
}
// We have completed expire of this session
expiring = false;
if ((manager != null) && (manager instanceof ManagerBase)) {
recycle();
}
}
  上面方法中从Session管理器移除该session实例并触发一些事件,同时设置一些内部变量的值。
  为了传递一个session对象给servlet实例,tomcat的session管理器会实例化StandardSession类,并填充该session对象;不过,为了阻止servlet程序员访问StandardSession实例中的一些敏感方法,tomcat容器将StandardSession实例封装为StandardSessionFacade类型的实例(实现HttpRequest接口的HttpRequestBase类的doGetSession方法里面调用session管理器的createSession()方法返回StandardSession类的实例,然后调用StandardSession实例 的getSession()方法返回StandardSessionFacade类型实例),该类仅仅实现了javax.servlet.http.HttpSession接口中的方法,这样servlet程序员就不能将HttpSession对象向下转型为StandardSession类型
  下面接下来描述session管理器,session管理器是org.apache.catalina.Manager接口的实例,抽象类ManagerBase实现了Manager接口,提供了常见功能的实现;ManagerBase类有两个直接子类,分别为StandardManager类和PersistentManagerBase类
  当tomcat运行时,StandardManager实例将session对象存储在内存中;当tomcat关闭时,它会将当前内存中所有的session对象序列化存储到文件中;当在此运行tomcat时,又会将这些session对象重新载入内存。
  另外,继承自PersistentManagerBase类的session管理器会将session对象存储到辅助存储器中,包括PersistentManager类和DistributedManager类(tomcat4中)
  下面是Manager接口的方法声明:



public interface Manager {

public Container getContainer();

public void setContainer(Container container);

public DefaultContext getDefaultContext();

public void setDefaultContext(DefaultContext defaultContext);

public boolean getDistributable();

public void setDistributable(boolean distributable);

public String getInfo();

public int getMaxInactiveInterval();

public void setMaxInactiveInterval(int interval);

public void add(Session session);

public void addPropertyChangeListener(PropertyChangeListener listener);

public Session createSession();

public Session findSession(String id) throws IOException;

public Session[] findSessions();

public void load() throws ClassNotFoundException, IOException;

public void remove(Session session);

public void removePropertyChangeListener(PropertyChangeListener listener);

public void unload() throws IOException;
}
  提供了setContainer()方法使用session管理器与Context容器相关联;一起一下添加、移除session实例的方法;设置session对象的最长存活时间;最后, load()方法与unload()方法用于从辅助存储器加载session对象到内存和序列化session对象并持久化到辅助存储器中。
  ManagerBase类为一个抽象类,提供了一些公共方法的实现,包括创建session对象、移除session对象等;这些活动的session对象都存储在一个名为sessions的HashMap变量中
  protected HashMap sessions = new HashMap();
  下面是创建session实例的方法



public Session createSession() {
// Recycle or create a Session instance
Session session = null;
synchronized (recycled) {
int size = recycled.size();
if (size > 0) {
session = (Session) recycled.get(size - 1);
recycled.remove(size - 1);
}
}
if (session != null)
session.setManager(this);
else
session = new StandardSession(this);
// Initialize the properties of the new session and return it
session.setNew(true);
session.setValid(true);
session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval(this.maxInactiveInterval);
String sessionId = generateSessionId();
String jvmRoute = getJvmRoute();
// @todo Move appending of jvmRoute generateSessionId()???
if (jvmRoute != null) {
sessionId += '.' + jvmRoute;
session.setId(sessionId);
}
/*
synchronized (sessions) {
while (sessions.get(sessionId) != null)        // Guarantee uniqueness
sessionId = generateSessionId();
}
*/
session.setId(sessionId);
return (session);
}
  上面创建的是StandardSession类型实例,在实现HttpRequest接口的HttpRequestBase类的相关方法中,调用getSession()方法返回StandardSessionFacade类型实例
  创建session对象需要调用受保护的方法返回session对象的唯一标识符



/**
* Generate and return a new session identifier.
*/
protected synchronized String generateSessionId() {
// Generate a byte array containing a session identifier
Random random = getRandom();
byte bytes[] = new byte[SESSION_ID_BYTES];
getRandom().nextBytes(bytes);
bytes = getDigest().digest(bytes);
// Render the result as a String of hexadecimal digits
StringBuffer result = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
byte b1 = (byte) ((bytes & 0xf0) >> 4);
byte b2 = (byte) (bytes & 0x0f);
if (b1 < 10)
result.append((char) ('0' + b1));
else
result.append((char) ('A' + (b1 - 10)));
if (b2 < 10)
result.append((char) ('0' + b2));
else
result.append((char) ('A' + (b2 - 10)));
}
return (result.toString());
}
  该标识符生成之后,会发送到客户端cookies里面,使客户端cookies里面的JSESSIONID值与之一致
  其他相关操作session对象方法如下,容易理解



    public void add(Session session) {
synchronized (sessions) {
sessions.put(session.getId(), session);
}
}
public Session findSession(String id) throws IOException {
if (id == null)
return (null);
synchronized (sessions) {
Session session = (Session) sessions.get(id);
return (session);
}
}

public Session[] findSessions() {
Session results[] = null;
synchronized (sessions) {
results = new Session[sessions.size()];
results = (Session[]) sessions.values().toArray(results);
}
return (results);
}

public void remove(Session session) {
synchronized (sessions) {
sessions.remove(session.getId());
}
}
  StandardManager类是Manager接口的标准实现,继承自上面的ManagerBase抽象类,同时实现了Lifecycle接口,这有可以由其相关联的Context容器来启动和关闭,在Context容器调用它的start()方法和stop()方法时,会调用load()从辅助存储器加载session对象到内存和调用unload()方法从内存持久化session对象到辅助存储器
  同时session管理器还负责销毁那些失效的session对象,这是由一个专门的线程来实现的,StandardManager类实现了Runnable接口



/**
* The background thread that checks for session timeouts and shutdown.
*/
public void run() {
// Loop until the termination semaphore is set
while (!threadDone) {
threadSleep();
processExpires();
}
}
  在线程休眠指定时间间隔后,调用processExpires()方法清理过期session对象



/**
* Invalidate all sessions that have expired.
*/
private void processExpires() {
long timeNow = System.currentTimeMillis();
Session sessions[] = findSessions();
for (int i = 0; i < sessions.length; i++) {
StandardSession session = (StandardSession) sessions;
if (!session.isValid())
continue;
int maxInactiveInterval = session.getMaxInactiveInterval();
if (maxInactiveInterval < 0)
continue;
int timeIdle = // Truncate, do not round up
(int) ((timeNow - session.getLastAccessedTime()) / 1000L);
if (timeIdle >= maxInactiveInterval) {
try {
session.expire();
} catch (Throwable t) {
log(sm.getString("standardManager.expireException"), t);
}
}
}
}
  我们可以看到,session对象的过期时间实际是session对象本身的成员变量的值
  int maxInactiveInterval = session.getMaxInactiveInterval()
  最后,servlet程序员需要调用javax.servlet.http.HttpSerletRequest接口的getSession()方法获取Session对象,当调用getSession()方法时,request对象必须调用与Context容器相关联的session管理器(创建session对象或返回一个已存在色session对象);request对象为了能够访问Session管理器,它必须能够访问Context容器。因此在SimpleWrapperValve类的invoke()方法中,需要调用org.apache.catalina.Request接口的setContext()方法传入Context容器实例



public void invoke(Request request, Response response, ValveContext valveContext)
throws IOException, ServletException {
SimpleWrapper wrapper = (SimpleWrapper) getContainer();
ServletRequest sreq = request.getRequest();
ServletResponse sres = response.getResponse();
Servlet servlet = null;
HttpServletRequest hreq = null;
if (sreq instanceof HttpServletRequest)
hreq = (HttpServletRequest) sreq;
HttpServletResponse hres = null;
if (sres instanceof HttpServletResponse)
hres = (HttpServletResponse) sres;
//-- new addition -----------------------------------
Context context = (Context) wrapper.getParent();
request.setContext(context);
//-------------------------------------
// Allocate a servlet instance to process this request
try {
servlet = wrapper.allocate();
if (hres!=null && hreq!=null) {
servlet.service(hreq, hres);
}
else {
servlet.service(sreq, sres);
}
}
catch (ServletException e) {
}
}
  同时在org.apache.catalina.connector.HttpRequestBase类的私有方法diGetSession()里面会调用Context接口的getManager()方法来获取session管理器对象



private HttpSession 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.getSession());

// 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) {
return (session.getSession());
}
}
// 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("httpRequestBase.createCommitted"));
}
session = manager.createSession();
if (session != null)
return (session.getSession());
else
return (null);
}
  注意里面实质是通过StandardSession实例的getSession()方法获取一个经过外观类StandardSessionFacade包装的HttpSession对象
  ---------------------------------------------------------------------------
  本系列How Tomcat Works系本人原创
  转载请注明出处 博客园 刺猬的温驯
  本人邮箱: chenying998179#163.com (#改为@)
  本文链接http://www.iyunv.com/chenying99/p/3237390.html

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-95436-1-1.html 上篇帖子: Tomcat部署项目 下篇帖子: Tomcat的bin目录下startup.bat、Tomcat6.exe、Tomcat6w.exe区别
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表