spring-session源码解读-5

简介: session通用策略Session在浏览器通常是通过cookie保存的,cookie里保存了jessionid,代表用户的session id。一个访问路径只有一个session cookie(事实上在客户端就只有一个cookie,jsessionid是作为cookie值的一部分,这里把cookie抽象成类似服务器端的实现),也就是一个访问路径在一个浏览器上只有一个se

session通用策略

Session在浏览器通常是通过cookie保存的,cookie里保存了jessionid,代表用户的session id。一个访问路径只有一个session cookie(事实上在客户端就只有一个cookie,jsessionid是作为cookie值的一部分,这里把cookie抽象成类似服务器端的实现),也就是一个访问路径在一个浏览器上只有一个session,这是绝大多数容器对session的实现。而spring却可以支持单浏览器多用户session。下面就看看spring是怎样去支持多用户session的。

对多用户session的支持

spring session通过增加session alias概念来实现多用户session,每一个用户都映射成一个session alias。当有多个session时,spring会生成“alias1 sessionid1 alias2 sessid2…….”这样的cookie值结构。

spring session提交时如果有新session生成,会触发onNewSession动作生成新的session cookie

public void onNewSession(Session session, HttpServletRequest request, HttpServletResponse response) {
        Set<String> sessionIdsWritten = getSessionIdsWritten(request);
        if(sessionIdsWritten.contains(session.getId())) {
            return;
        }
        sessionIdsWritten.add(session.getId());

        Map<String,String> sessionIds = getSessionIds(request);
        String sessionAlias = getCurrentSessionAlias(request);
        sessionIds.put(sessionAlias, session.getId());
        Cookie sessionCookie = createSessionCookie(request, sessionIds);
        response.addCookie(sessionCookie);
    }

a) 确保已经存在cookie里的session不会再被处理。
b) 生成一个包含所有alias的session id的map,并通过这个map构造新的session cookie值。

createSessionCookie会根据一个alias-sessionid的map去构造session cookie。

private Cookie createSessionCookie(HttpServletRequest request,
            Map<String, String> sessionIds) {
        //cookieName是"SESSION",spring的session cookie都是
        //以"SESSION"命名的
        Cookie sessionCookie = new Cookie(cookieName,"");
        //省略部分非关键逻辑

        if(sessionIds.isEmpty()) {
            sessionCookie.setMaxAge(0);
            return sessionCookie;
        }

        if(sessionIds.size() == 1) {
            String cookieValue = sessionIds.values().iterator().next();
            sessionCookie.setValue(cookieValue);
            return sessionCookie;
        }
        StringBuffer buffer = new StringBuffer();
        for(Map.Entry<String,String> entry : sessionIds.entrySet()) {
            String alias = entry.getKey();
            String id = entry.getValue();

            buffer.append(alias);
            buffer.append(" ");
            buffer.append(id);
            buffer.append(" ");
        }
        buffer.deleteCharAt(buffer.length()-1);

        sessionCookie.setValue(buffer.toString());
        return sessionCookie;
    }

a) 当session被invalidate,可能会存在seesionids为空的情况,这种情况下将session cookie的最大失效时间设成立即。
b) 如果只有一个session id,则和普通session cookie一样处理,cookie值就是session id。
c) 如果存在多个session id,则生成前文提到的session cookie值结构。

session cookie的获取

getSessionIds方法会取出request里的session cookie值,并且对每种可能的值结构进行相应的格式化生成一个key-value的map。

public Map<String,String> getSessionIds(HttpServletRequest request) {
        Cookie session = getCookie(request, cookieName);
        String sessionCookieValue = session == null ? "" : session.getValue();
        Map<String,String> result = new LinkedHashMap<String,String>();
        StringTokenizer tokens = new StringTokenizer(sessionCookieValue, " ");
        //单用户cookie的情况
        if(tokens.countTokens() == 1) {
            result.put(DEFAULT_ALIAS, tokens.nextToken());
            return result;
        }
        while(tokens.hasMoreTokens()) {
            String alias = tokens.nextToken();
            if(!tokens.hasMoreTokens()) {
                break;
            }
            String id = tokens.nextToken();
            result.put(alias, id);
        }
        return result;
    }
  1. 对单用户session cookie的处理,只取出值,默认为是默认别名(默认为0)用户的session。
  2. 对多用户,则依据值结构的格式生成alias-sessionid的map。
  3. 以上两种格式化都是对创建session的逆操作。

getCurrentSessionAlias用来获取当前操作用户。可以通过在request里附加alias信息,从而让spring可以判断是哪个用户在操作。别名是通过”alias name=alias”这样的格式传入的,alias name默认是_s,可以通过setSessionAliasParamName(String)方法修改。我们可以在url上或者表单里添加”_s=your user alias”这样的形式来指明操作用户的别名。如果不指明用户别名,则会认为是默认用户,可以通过setSessionAliasParamName(null)取消别名功能。

public String getCurrentSessionAlias(HttpServletRequest request) {
        if(sessionParam == null) {
            return DEFAULT_ALIAS;
        }
        String u = request.getParameter(sessionParam);
        if(u == null) {
            return DEFAULT_ALIAS;
        }
        if(!ALIAS_PATTERN.matcher(u).matches()) {
            return DEFAULT_ALIAS;
        }
        return u;
    }

触发session提交

spring会通过两个方面确保session提交:

a) response提交,主要包括response的sendRedirect和sendError以及其关联的字节字符流的flush和close方法。

abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
    public OnCommittedResponseWrapper(HttpServletResponse response) {
        super(response);
    }
    /**
     * Implement the logic for handling the {@link javax.servlet.http.HttpServletResponse} being committed
     */
    protected abstract void onResponseCommitted();

    @Override
    public final void sendError(int sc) throws IOException {
        doOnResponseCommitted();
        super.sendError(sc);
    }
    //sendRedirect处理类似sendError
    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return new SaveContextServletOutputStream(super.getOutputStream());
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        return new SaveContextPrintWriter(super.getWriter());
    }

    private void doOnResponseCommitted() {
        if(!disableOnCommitted) {
            onResponseCommitted();
            disableOnResponseCommitted();
        } else if(logger.isDebugEnabled()){
            logger.debug("Skip invoking on");
        }
    }

    private class SaveContextPrintWriter extends PrintWriter {
        private final PrintWriter delegate;

        public SaveContextPrintWriter(PrintWriter delegate) {
            super(delegate);
            this.delegate = delegate;
        }

        public void flush() {
            doOnResponseCommitted();
            delegate.flush();
        }
//close方法与flush方法类似
    }
//SaveContextServletOutputStream处理同字符流
}

onResponseCommitted的实现由子类SessionRepositoryResponseWrapper提供

private final class SessionRepositoryResponseWrapper extends OnCommittedResponseWrapper {

        private final SessionRepositoryRequestWrapper request;
        /**
         * @param response the response to be wrapped
         */
        public SessionRepositoryResponseWrapper(SessionRepositoryRequestWrapper request, HttpServletResponse response) {
            super(response);
            if(request == null) {
                throw new IllegalArgumentException("request cannot be null");
            }
            this.request = request;
        }

        @Override
        protected void onResponseCommitted() {
            request.commitSession();
        }
    }

response提交后触发了session提交。
b) SessionRespositoryFilter
仅仅通过response提交时触发session提交并不能完全保证session的提交,有些情况下不会触发response提交,比如对相应资源的访问没有servlet处理,这种情况就需要通过全局filter做保证。

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
        //省略
        //filterChain会在所有filter都执行完毕后调用对应的servlet
            filterChain.doFilter(strategyRequest, strategyResponse);
        } finally {
        //所有的处理都完成后提交session
            wrappedRequest.commitSession()
        }   
目录
相关文章
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
93 2
|
2月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
101 5
|
1天前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
1月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
57 2
|
2月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
73 9
|
4月前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
237 24
|
3月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
196 5
|
3月前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
3月前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
155 9
|
3月前
|
设计模式 JavaScript Java
Spring 事件监听机制源码
Spring 提供了事件发布订阅机制,广泛应用于项目中。本文介绍了如何通过自定义事件类、订阅类和发布类实现这一机制,并展示了如何监听 SpringBoot 启动过程中的多个事件(如 `ApplicationStartingEvent`、`ApplicationEnvironmentPreparedEvent` 等)。通过掌握这些事件,可以更好地理解 SpringBoot 的启动流程。示例代码展示了从事件发布到接收的完整过程。
下一篇
开通oss服务