博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Tomcat的Session管理(一)
阅读量:5257 次
发布时间:2019-06-14

本文共 12468 字,大约阅读时间需要 41 分钟。

摘要:本文目的在介绍tomcat中session相关的架构以及session的查询。

在Servlet开发中,Session代表用户会话,开发人员经常使用Session来临时存储一些信息,那么Session到底是什么,Tomcat中是如何对Session进行管理的,我们今天到源码中查看下。

查看相关资料,我们先看下Session相关的类图

818454-20161019173033998-1905820039.jpg

从图上可以看到Session对应的接口有两个SessionHttpSessionStandardSession实现了这2个接口,StandardSessionFacade实现了HttpSessionStandardSessionFacede包含了StandardSession。看起来有点饶,我们详细讲解下每个类的含义。

  • HttpSession

我们都知道Tomcat是支持Servlet规范的web容器,所以内部会包含一些Servlet的内容。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

在Catalina中,Session由Session管理器组件负责管理,例如创建和销毁Session管理器是org.apache.catalina.Manager接口的实例。我们来看看Manager管理器相关的类图。

818454-20161023194551904-795881013.png

Manager

Catalina中session管理器概念的顶层接口。其中定义了一些对session的基本操作,如创建,查找,添加,移除以及对特定session对象的属性的修改。除了基本操作以外还定义了一些特殊的例如session的序列化反序列化等等。

ManagerBase

抽象类,对Manager接口作了基本实现,方便继承类的复写以及实现,就像其他xxxBase类起到了一样的作用。

StandardManager

Catalina中默认的Session管理器的实现类,具体功能我们下面分析。

PersistentManagerBase

Session管理器中打标持久化Session的父类,虽然StandardManager也可以将Session持久化,但是只是将Session持久化为一个文件(后面会说到),PersistentManagerBase类和StandardManager类的区别在于前者的存储器的表现形式可以有多种,比如数据库,文件等(具体后面讨论)

PersistentManager

PersistentManager基础上增加了两个属性。

DistributedManager

PersistentManagerBaseBackupManager类中抽象出来的一个接口,这个接口表示了两者一个共同的属性:不会持有所有session的对象,但是能找到所有的session。例如PersistentManagerBase可以将session同时存放在内存和持久化介质中。

ClusterManager

分布式集群session处理器父类,定义了一些基本方法例如获取所有tomcat集群的机器,获取其他集群的信息等基本功能。

ClusterManagerBase

抽象类,对ClusterManager作了基本实现。

BackupManager

集群间session复制策略的一种实现,会话数据只有一个备份节点,这个备份节点的位置集群中所有节点都可见。

DeltaManager

集群建session复制策略的一种实现,采用的方式是只复制差异部分,是分布式集群session同步中最好的同步方式。

了解一些基本信息后,我们来查看我们最常用的getSession()方法,从而来了解session运作的方式。

在Servlet中我们使用HttpServletRequestgetSession()方法来获取session对象,而真正执行getSession方法的其实是org.apache.catalina.connector.RequestFacade对象

public class RequestFacade implements HttpServletRequest

RequestFacade对象实现了HttpServletRequest内部封装了org.apache.catalina.connector.Request对象,我们查看getSession()方法:

@Overridepublic HttpSession getSession() {    if (request == null) {        throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));    }    return getSession(true);}@Overridepublic 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()方法,源码如下:

@Overridepublic HttpSession getSession(boolean create) {    Session session = doGetSession(create);    if (session == null) {        return null;    }    return session.getSession();}

下面我们看doGetSession()方法,由于源码过长,删减了部分内容,保留了核心代码。

protected Session doGetSession(boolean create) {    //判断context    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            //111111111111            session = manager.findSession(requestedSessionId);        } catch (IOException e) {            session = null;        }        //判断获取到的session是否有效        if ((session != null) && !session.isValid()) {            session = null;        }                if (session != null) {            //session有效 增加访问时间和次数            session.access();            return (session);        }    }        //如果没有找到已存在的session并且 要求创建新session    //获取此次request对应的sessionid    String sessionId = getRequestedSessionId();    //。。。略部分代码    //222222222222    session = manager.createSession(sessionId);    //创建cookie    Cookie cookie =ApplicationSessionCookieConfig.createSessionCookie(context, session.getIdInternal(),isSecure());    //将cookie设置到response 中    response.addSessionCookieInternal(cookie);        if (session == null) {        return null;    }    //增加访问时间和次数    session.access();    return session;}

代码很简单,思路也很清晰,很容易理解,这里有3处需要关注下。

在代码1的地方,调用了manager.findSession(),requestedSessionId是个String类型的字符串(稍后说如何创建一个这样的字符串)。在Catalina中默认的是StandardManager,所以查看StandardManagerfindSession()方法,最后在其父类ManagerBase中找到相关方法:

@Overridepublic Session findSession(String id) throws IOException {    if (id == null) {        return null;    }    return sessions.get(id);} /** * The set of currently active Sessions for this Manager, keyed by * session identifier. */protected Map
sessions = new ConcurrentHashMap
();

从源码可以得出结论,一个类型的session管理器,他都有一个总的session容器,就是一个ConcurrentHashMap实例,key是sessionId,value是对应的session对象。

在代码2的地方由于在已有的session的map中没有找到session,所以需要创建一个新的session,createSession()源码如下(在ManagerBase类中):

@Overridepublic 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)中往下查看可以看到:

sessions.put(session.getIdInternal(), session);

也就是把新创建的session放入到管理器的session容器中(ConcurrentHashMap)对象。

doGetSession方法中最后需要关注的就是requestedSessionId是如何生成的,我们先看下定义

/** * The requested session ID (if any) for this request. */protected String requestedSessionId = null;

可以看出是个字符串类型的标识符并且和request有关,由于tomcat的请求流程我们还没梳理到,所以这里直接给出生成的方法。

首先,这个所谓的requestedSessionId对应的就是我们每次请求都有一个唯一session标识符,为了以后同一个用户再次请求的时候可以使用同一个session(会话)。既然跟request有关,那么就可以查看request相关方法,而之前在讲解启动的时候提到过Connector是专门来处理请求的,而对应http请求的ConnectorHttp11Processor。首先用户发送一个http请求传递给Http11Processor,经由Http11Processor解析封装在 org.apache.coyote.Request然后传递给CoyoteAdaptercoyoteAdapter是一个适配器,将coyote框 架封装的org.apache.coyote.Request适配给org.apache.catalina.connector.Request,而解析的过程就在CoyoteAdapter中。

解析requestSessionId分为两步,一个是从请求url中解析,另一步是从cookie中解析,之所以需要解析两次是因为有的用户会禁用cookie,而禁用cookie的时候sessionId则会带在请求url中(其实就是我们熟知的jsessionId)

从请求url中解析(只保留了核心代码)

首先如果sessionId保留在url中是以如下形式

http://xxx.com?a=1&b=1&c=1;k1=v1;k2=v2;jsessionId=xxxx

可以看出requestSessionId是保存在;之后的,之所以解释下 是方便理解。

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);        }        //22222222222        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);    }}

代码还算容易理解,比较清楚的都标注在代码中了,从标注1到标注2的地方主要做的就是获取一对kv结构(k1=v1这样的结构)。而在代码2的判断的地方可以看到很明显request.addPathParameter(name, value);,request把kv都设置进去了,这其中就包括了jsessionId,以上就是在解析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());                }            }        }    }}

代码比较简单,以上就是requestSessionId是如何设置的。

看到这里,doGetSession()方法我们就看完了,可以看到它返回的是一个StandardSession对象,最后在getSession()方法中返回的是StandardSession对象的getSession()方法,查看下源码。

@Overridepublic HttpSession getSession() {    Session session = doGetSession(true);    if (session == null) {        return null;    }    return session.getSession();} /** * Return the HttpSession for which this object * is the facade. */@Overridepublic HttpSession getSession() {    if (facade == null){        if (SecurityUtil.isPackageProtectionEnabled()){            final StandardSession fsession = this;            facade = AccessController.doPrivileged(                    new PrivilegedAction
(){ @Override public StandardSessionFacade run(){ return new StandardSessionFacade(fsession); } }); } else { facade = new StandardSessionFacade(this); } } return (facade);}

可以看出最后getSession()方法返回的并不是StandardSession对象,而是StandardSession对象的外观类StandardSessionFacade。这也和我们一开始介绍session类图的时候描述的一致。

转载于:https://www.cnblogs.com/coldridgeValley/p/6016211.html

你可能感兴趣的文章
大数据学习(2)- export、source(附带多个服务器一起启动服务器)
查看>>
Pandas matplotlib 无法显示中文 Ubuntu16.04
查看>>
hyperopt中文文档:Installation-Notes安装说明
查看>>
Python3 根据关键字爬取百度图片
查看>>
流和打印控件用法
查看>>
Python 基础之-常用模块
查看>>
选择排序的算法
查看>>
知乎原理
查看>>
[leetcode]3. Longest Substring Without Repeating Characters
查看>>
异常处理
查看>>
实现VS2010整合NUnit进行单元测试(转载)
查看>>
python 2.0 s12 day5 常用模块介绍
查看>>
Lec2-KMP实现
查看>>
MFC学习之路之多媒体 --(3) 视频会议
查看>>
k近邻模型
查看>>
Python标准库12 数学与随机数 (math包,random包)(转载)
查看>>
算法53----换钱的最小次数和方法数【动态规划】
查看>>
javascript 面向过程和面向对象
查看>>
roboguide的夹具制作
查看>>
软件测试作业4
查看>>