• Tomcat 源码分析 (Tomcat的Session管理) (十一)


    1.Session的问题

    Tomcat中对于Session相关的框架和查询Session

    Servlet中, Session代表着用户会话, 开发人员经常用Session存储一些数据

    1. Session是什么
    2. Tomcat如何对Session进行管理的。

    2.Session关键类分析

    在这里插入图片描述
    两个接口: SessionHttpSession, 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
      Catalinasession管理器概念的顶层接口。其中定义了一些对session基本操作,如创建,查找,添加,移除以及对特定session对象的属性的修改。除了基本操作以外还定义了一些特殊的例如session的序列化反序列化等等。

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

    • StandardManager
      Catalina中默认的Session管理器的实现类

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

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

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

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

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

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

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

    3.Session获取细节

    Servlet中我们使用HttpServletRequestgetSession()方法来获取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);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    最终调用的还是org.apache.catalina.connector.Request对象的getSession()方法

        @Override
        public HttpSession getSession(boolean create) {
            Session session = doGetSession(create);
            if (session == null) {
                return null;
            }
    
            return session.getSession();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    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<>();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    一个类型的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);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    创建新的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;
    
    • 1
    • 2
    • 3
    • 4

    request有关, requestedSessionId对应的就是我们每次请求都有一个唯一session标识符, 为了以后同一个用户再次请求的时候可以使用同一个session

    用户发送一个http请求传递给Http11Processor,经由Http11Processor解析封装在 org.apache.coyote.Request然后传递给CoyoteAdaptercoyoteAdapter是一个适配器,将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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    解析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());
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    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);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    可以看出最后getSession()方法返回的并不是StandardSession对象,而是StandardSession对象的外观类StandardSessionFacade

    之所以存在这个类是因为不想直接让开发人员操作到StandardSession,这样可以保证类的安全。

  • 相关阅读:
    4K高清视频素材,拿去不谢。
    【异常】com.alicp.jetcache.support.CacheEncodeException: Java Encode error.
    Ubuntu系统之管理文件权限一
    前端必备:Node.js是什么?它有哪些特点和优势?
    linux创建sftp用户
    golang获取操作系统信息:CPU,内存,网络,磁盘,进程管理,传感器(温度,风扇,电池)
    Python爬虫入门
    NNDL 实验八 网络优化与正则化(5)逐层规范化
    备忘录模式(Memento)
    springboot的配置变量
  • 原文地址:https://blog.csdn.net/qq_43141726/article/details/126045852