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

文章目录

      • 1.Session的问题
      • 2.Session关键类分析
      • 3.Session获取细节

1.Session的问题

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

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

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

2.Session关键类分析

Tomcat 源码分析 (Tomcat的Session管理) (十一)_第1张图片
两个接口: 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

Tomcat 源码分析 (Tomcat的Session管理) (十一)_第2张图片

  • 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);
        }
    }

最终调用的还是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);

Tomcat 源码分析 (Tomcat的Session管理) (十一)_第3张图片
也就是把新创建的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然后传递给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);
    }
}

解析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,这样可以保证类的安全。

你可能感兴趣的:(Tomcat,源码分析,tomcat,servlet,java)