长期以来,session 管理就是企业级 Java 中的一部分,以致于我们潜意识就认为它是已经解决的问题,在最近的记忆中,我们没有看到这个领域有很大的革新。
但是,现代的趋势是微服务以及可水平扩展的原生云应用(cloud native application),它们会挑战过去 20 多年来我们设计和构建 session 管理器时的前提假设,并且暴露了现代化 session 管理器的不足。
本文将会阐述最近发布的 Spring Session API 如何帮助我们克服眼下 session 管理方式中的一些不足,在企业级 Java 中,传统上都会采用这种旧的方式。我们首先会简单阐述一下当前 session 管理中的问题,然后深入介绍 Spring Session 是如何解决这些问题的。在文章的最后,将会详细展示 Spring Session 是如何运行的,以及在项目中怎样使用它。
Spring Session 为企业级 Java 应用的 session 管理带来了革新,使得以下的功能更加容易实现:
- 编写可水平扩展的原生云应用。
- 将 session 所保存的状态卸载到特定的外部 session 存储中,如 Redis 或 Apache Geode 中,它们能够以独立于应用服务器的方式提供高质量的集群。
- 当用户使用 WebSocket 发送请求的时候,能够保持 HttpSession 处于活跃状态。
- 在非 Web 请求的处理代码中,能够访问 session 数据,比如在 JMS 消息的处理代码中。
- 支持每个浏览器上使用多个 session,从而能够很容易地构建更加丰富的终端用户体验。
- 控制 session id 如何在客户端和服务器之间进行交换,这样的话就能很容易地编写 Restful API,因为它可以从 HTTP 头信息中获取 session id,而不必再依赖于 cookie。
需要说明的很重要的一点就是,Spring Session 的核心项目并不依赖于 Spring 框架,所以,我们甚至能够将其应用于不使用 Spring 框架的项目中。
传统 session 管理的问题
传统的 JavaEE session 管理会有各种问题,这恰好是 Spring Session 所要试图解决的。这些问题在下面以样例的形式进行了阐述。
构建可水平扩展的原生云应用
在原生的云应用架构中,会假设应用能够进行扩展,这是通过在 Linux 容器中运行更多的应用程序实例实现的,这些容器会位于一个大型的虚拟机池中。例如,我们可以很容易地将一个“.war”文件部署到位于 Cloud Foundry 或 Heroku 的 Tomcat 中,然后在几秒钟的时间内就能扩展到 100 个应用实例,每个实例可以具有 1GB RAM。我们还可以配置云平台,基于用户的需求自动增加和减少应用实例的数量。
在很多的应用服务器中,都会将 HTTP session 状态保存在 JVM 中,这个 JVM 与运行应用程序代码的 JVM 是同一个,因为这样易于实现,并且速度很快。当新的应用服务器实例加入或离开集群时,HTTP session 会基于现有的应用服务器实例进行重新平衡。在弹性的云环境中,我们会拥有上百个应用服务器实例,并且实例的数量可能在任意时刻增加或减少,这样的话,我们就会遇到一些问题:
- 重平衡 HTTP session 可能会成为性能瓶颈。
- 为了存储大量的 session,会需要很大的堆空间,这会导致垃圾收集,从而对性能产生负面影响。
- 云基础设施通常会禁止 TCP 多播(multicast),但是 session 管理器常常会使用这种机制来发现哪一个应用服务器实例加入或离开了集群。
因此,更为高效的办法是将 HTTP session 状态保存在独立的数据存储中,这个存储位于运行应用程序代码的 JVM 之外。例如,我们可以将 100 个 Tomcat 实例配置为使用 Redis 来存储 session 状态,当 Tomcat 实例增加或减少的时候,Redis 中所存储的 session 并不会受到影响。同时,因为 Redis 是使用 C 语言编写的,所以它可以使用上百 GB 甚至 TB 级别的 RAM,它不会涉及到垃圾收集的问题。
对于像 Tomcat 这样的开源服务器,很容易找到 session 管理器的替代方案,这些替代方案可以使用外部的数据存储,如 Redis 或 Memcached。但是,这些配置过程可能会比较复杂,而且每种应用服务器都有所差别。对于闭源的产品,如 WebSphere 和 Weblogic,寻找它们的 session 管理器替代方案不仅非常困难,在有些时候,甚至是无法实现的。
Spring Session 提供了一种独立于应用服务器的方案,这种方案能够在 Servlet 规范之内配置可插拔的 session 数据存储,不依赖于任何应用服务器的特定 API。这就意味着 Spring Session 能够用于实现了 servlet 规范的所有应用服务器之中(Tomcat、Jetty、 WebSphere、WebLogic、JBoss 等),它能够非常便利地在所有应用服务器中以完全相同的方式进行配置。我们还可以选择任意最适应需求的外部 session 数据存储。这使得 Spring Session 成为一个很理想的迁移工具,帮助我们将传统的 JavaEE 应用转移到云中,使其成为满足 12-factor 的应用。
每个用户有多个账号
假设我们在 example.com 上运行面向公众的 Web 应用,在这个应用中有些用户会创建多个账号。例如,用户 Jeff Lebowski 可能会有两个账户 thedude@example.com 和 lebowski@example.com。和其他 Java Web 应用一样,我们会使用HttpSession
来跟踪应用的状态,如当前登录的用户。所以,当用户希望从 thedude@example.com 切换到 lebowski@example.com 时,他必须要首先退出,然后再重新登录回来。
借助 Spring Session,为每个用户配置多个 HTTP session 会非常容易,这样用户在 thedude@example.com 和 lebowski@example.com 之间切换的时候,就不需要退出和重新登录了。
多级别的安全预览
假设我们正在构建的 Web 应用有一个复杂、自定义的权限功能,其中应用的 UI 会基于用户所授予的角色和权限实现自适应。
例如,假设应用有四个安全级别:public、confidential、secret 和 top secret。当用户登录应用之后,系统会判断用户所具有的最高安全级别并且只会显示该级别和该级别之下的数据。所以,具有 public 权限的用户只能看到 public 级别的文档,具有 secret 权限的用户能够看到 public、confidential 和 secret 级别的文档,诸如此类。为了保证用户界面更加友好,应用程序应该允许用户预览在较低的安全级别条件下页面是什么样子的。例如,top secret 权限的用户能够将应用从 top secret 模式切换到 secret 模式,这样就能站在具有 secret 权限用户的视角上,查看应用是什么样子的。
典型的 Web 应用会将当前用户的标识及其角色保存在 HTTP session 中,但因为在 Web 应用中,每个登录的用户只能有一个 session,因此除了用户退出并重新登录进来,我们并没有办法在角色之间进行切换,除非我们为每个用户自行实现多个 session 的功能。
借助 Spring Session,可以很容易地为每个登录用户创建多个 session,这些 session 之间是完全独立的,因此实现上述的预览功能是非常容易的。例如,当前用户以 top secret 角色进行了登录,那么应用可以创建一个新的 session,这个 session 的最高安全角色是 secret 而不是 top secret,这样的话,用户就可以在 secret 模式预览应用了。
当使用 Web Socket 的时候保持登录状态
假设用户登录了 example.com 上的 Web 应用,那么他们可以使用 HTML5 的 chat 客户端实现聊天的功能,这个客户端构建在 websocket 之上。按照 servlet 规范,通过 websocket 传入的请求并不能保持 HTTP session 处于活跃状态,所以当用户在聊天的过程中,HTTP session 的倒数计时器会在不断地流逝。即便站在用户的立场上,他们一直在使用应用程序,HTTP session 最终也可能会出现过期。当 HTTP session 过期时,websocket 连接将会关闭。
借助 Spring Session,对于系统中的用户,我们能够很容易地实现 websocket 请求和常规的 HTTP 请求都能保持 HTTP session 处于活跃状态。
非 Web 请求访问 Session 数据
假设我们的应用提供了两种访问方式:一种使用基于 HTTP 的 REST API,而另一种使用基于 RabbitMQ 的 AMQP 消息。执行消息处理代码的线程将无法访问应用服务器的 HttpSession,所以我们必须要以一种自定义的方案来获取 HTTP session 中的数据,这要通过自定义的机制来实现。
通过使用 Spring Session,只要我们能够知道 session 的 id,就可以在应用的任意线程中访问 Spring Session。因此,Spring Session 具备比 Servlet HTTP session 管理器更为丰富的 API,只要知道了 session id,我们就能获取任意特定的 session。例如,在一个传入的消息中可能会包含用户 id 的 header 信息,借助它,我们就可以直接获取 session 了。
Spring Session 是如何运行的
我们已经讨论了在传统的应用服务器中,HTTP session 管理存在不足的各种场景,接下来看一下 Spring Session 是如何解决这些问题的。
Spring Session 的架构
当实现 session 管理器的时候,有两个必须要解决的核心问题。首先,如何创建集群环境下高可用的 session,要求能够可靠并高效地存储数据。其次,不管请求是 HTTP、WebSocket、AMQP 还是其他的协议,对于传入的请求该如何确定该用哪个 session 实例。实质上,关键问题在于:在发起请求的协议上,session id 该如何进行传输?
Spring Session 认为第一个问题,也就是在高可用可扩展的集群中存储数据已经通过各种数据存储方案得到了解决,如 Redis、GemFire 以及 Apache Geode 等等,因此,Spring Session 定义了一组标准的接口,可以通过实现这些接口间接访问底层的数据存储。Spring Session 定义了如下核心接口:Session、ExpiringSession
以及SessionRepository
,针对不同的数据存储,它们需要分别实现。
org.springframework.session.Session
接口定义了 session 的基本功能,如设置和移除属性。这个接口并不关心底层技术,因此能够比 servlet HttpSession 适用于更为广泛的场景中。org.springframework.session.ExpiringSession
扩展了 Session 接口,它提供了判断 session 是否过期的属性。RedisSession 是这个接口的一个样例实现。org.springframework.session.SessionRepository
定义了创建、保存、删除以及检索 session 的方法。将 Session 实例真正保存到数据存储的逻辑是在这个接口的实现中编码完成的。例如,RedisOperationsSessionRepository 就是这个接口的一个实现,它会在 Redis 中创建、存储和删除 session。
Spring Session 认为将请求与特定的 session 实例关联起来的问题是与协议相关的,因为在请求 / 响应周期中,客户端和服务器之间需要协商同意一种传递 session id 的方式。例如,如果请求是通过 HTTP 传递进来的,那么 session 可以通过 HTTP cookie 或 HTTP Header 信息与请求进行关联。如果使用 HTTPS 的话,那么可以借助 SSL session id 实现请求与 session 的关联。如果使用 JMS 的话,那么 JMS 的 Header 信息能够用来存储请求和响应之间的 session id。
对于 HTTP 协议来说,Spring Session 定义了HttpSessionStrategy
接口以及两个默认实现,即CookieHttpSessionStrategy
和HeaderHttpSessionStrategy
,其中前者使用 HTTP cookie 将请求与 session id 关联,而后者使用 HTTP header 将请求与 session 关联。
如下的章节详细阐述了 Spring Session 使用 HTTP 协议的细节。
在撰写本文的时候,在当前的 Spring Session 1.0.2 GA 发布版本中,包含了 Spring Session 使用 Redis 的实现,以及基于 Map 的实现,这个实现支持任意的分布式 Map,如 Hazelcast。让 Spring Session 支持某种数据存储是相当容易的,现在有支持各种数据存储的社区实现。
Spring Session 对 HTTP 的支持
Spring Session 对 HTTP 的支持是通过标准的 servlet filter 来实现的,这个 filter 必须要配置为拦截所有的 web 应用请求,并且它应该是 filter 链中的第一个 filter。Spring Session filter 会确保随后调用javax.servlet.http.HttpServletRequest
的getSession()
方法时,都会返回 Spring Session 的HttpSession
实例,而不是应用服务器默认的 HttpSession。
如果要理解它的话,最简单的方式就是查看 Spring Session 实际所使用的源码。首先,我们了解一下标准 servlet 扩展点的一些背景知识,在实现 Spring Session 的时候会使用这些知识。
在 2001 年,Servlet 2.3 规范引入了ServletRequestWrapper
。它的javadoc 文档这样写道,ServletRequestWrapper
“提供了ServletRequest
接口的便利实现,开发人员如果希望将请求适配到 Servlet 的话,可以编写它的子类。这个类实现了包装(Wrapper)或者说是装饰(Decorator)模式。对方法的调用默认会通过包装的请求对象来执行”。如下的代码样例抽取自 Tomcat,展现了 ServletRequestWrapper 是如何实现的。
public class ServletRequestWrapper implements ServletRequest { private ServletRequest request; /** * 创建 ServletRequest 适配器,它包装了给定的请求对象。 * @throws java.lang.IllegalArgumentException if the request is null */ public ServletRequestWrapper(ServletRequest request) { if (request == null) { throw new IllegalArgumentException("Request cannot be null"); } this.request = request; } public ServletRequest getRequest() { return this.request; } public Object getAttribute(String name) { return this.request.getAttribute(name); } // 为了保证可读性,其他的方法删减掉了 }
Servlet 2.3 规范还定义了HttpServletRequestWrapper
,它是ServletRequestWrapper
的子类,能够快速提供HttpServletRequest
的自定义实现,如下的代码是从 Tomcat 抽取出来的,展现了HttpServletRequesWrapper
类是如何运行的。
public class HttpServletRequestWrapper extends ServletRequestWrapper implements HttpServletRequest { public HttpServletRequestWrapper(HttpServletRequest request) { super(request); } private HttpServletRequest _getHttpServletRequest() { return (HttpServletRequest) super.getRequest(); } public HttpSession getSession(boolean create) { return this._getHttpServletRequest().getSession(create); } public HttpSession getSession() { return this._getHttpServletRequest().getSession(); } // 为了保证可读性,其他的方法删减掉了 }
所以,借助这些包装类就能编写代码来扩展HttpServletRequest
,重载返回HttpSession
的方法,让它返回由外部存储所提供的实现。如下的代码是从 Spring Session 项目中提取出来的,但是我将原来的注释替换为我自己的注释,用来在本文中解释代码,所以在阅读下面的代码片段时,请留意注释。
/* * 注意,Spring Session 项目定义了扩展自 * 标准 HttpServletRequestWrapper 的类,用来重载 * HttpServletRequest 中与 session 相关的方法。 */ private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper { private HttpSessionWrapper currentSession; private Boolean requestedSessionIdValid; private boolean requestedSessionInvalidated; private final HttpServletResponse response; private final ServletContext servletContext; /* * 注意,这个构造器非常简单,它接受稍后会用到的参数, * 并且委托给它所扩展的 HttpServletRequestWrapper */ private SessionRepositoryRequestWrapper( HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) { super(request); this.response = response; this.servletContext = servletContext; } /* * 在这里,Spring Session 项目不再将调用委托给 * 应用服务器,而是实现自己的逻辑, * 返回由外部数据存储作为支撑的 HttpSession 实例。 * * 基本的实现是,先检查是不是已经有 session 了。如果有的话, * 就将其返回,否则的话,它会检查当前的请求中是否有 session id。 * 如果有的话,将会根据这个 session id,从它的 SessionRepository 中加载 session。 * 如果 session repository 中没有 session,或者在当前请求中, * 没有当前 session id 与请求关联的话, * 那么它会创建一个新的 session,并将其持久化到 session repository 中。 */ @Override public HttpSession getSession(boolean create) { if(currentSession != null) { return currentSession; } String requestedSessionId = getRequestedSessionId(); if(requestedSessionId != null) { S session = sessionRepository.getSession(requestedSessionId); if(session != null) { this.requestedSessionIdValid = true; currentSession = new HttpSessionWrapper(session, getServletContext()); currentSession.setNew(false); return currentSession; } } if(!create) { return null; } S session = sessionRepository.createSession(); currentSession = new HttpSessionWrapper(session, getServletContext()); return currentSession; } @Override public HttpSession getSession() { return getSession(true); } }
Spring Session 定义了SessionRepositoryFilter
,它实现了 Servlet Filter
接口。我抽取了这个 filter 的关键部分,将其列在下面的代码片段中,我还添加了一些注释,用来在本文中阐述这些代码,所以,同样的,请阅读下面代码的注释部分。
/* * SessionRepositoryFilter 只是一个标准的 ServletFilter, * 它的实现扩展了一个 helper 基类。 */ public class SessionRepositoryFilter < S extends ExpiringSession > extends OncePerRequestFilter { /* * 这个方法是魔力真正发挥作用的地方。这个方法创建了 * 我们上文所述的封装请求对象和 * 一个封装的响应对象,然后调用其余的 filter 链。 * 这里,关键在于当这个 filter 后面的应用代码执行时, * 如果要获得 session 的话,得到的将会是 Spring Session 的 * HttpServletSession 实例,它是由后端的外部数据存储作为支撑的。 */ protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { request.setAttribute(SESSION_REPOSITORY_ATTR, sessionRepository); SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request,response,servletContext); SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest, response); HttpServletRequest strategyRequest = httpSessionStrategy.wrapRequest(wrappedRequest, wrappedResponse); HttpServletResponse strategyResponse = httpSessionStrategy.wrapResponse(wrappedRequest, wrappedResponse); try { filterChain.doFilter(strategyRequest, strategyResponse); } finally { wrappedRequest.commitSession(); } } }
我们从这一章节得到的关键信息是,Spring Session 对 HTTP 的支持所依靠的是一个简单老式的ServletFilter
,借助 servlet 规范中标准的特性来实现 Spring Session 的功能。因此,我们能够让已有的 war 文件使用 Spring Session 的功能,而无需修改已有的代码,当然如果你使用javax.servlet.http.HttpSessionListener
的话,就另当别论了。Spring Session 1.0 并不支持HttpSessionListener
,但是 Spring Session 1.1 M1 发布版本已经添加了对它的支持,你可以通过该地址了解更多细节信息。
配置 Spring Session
在 Web 项目中配置 Spring Session 分为四步:
- 搭建用于 Spring Session 的数据存储
- 将 Spring Session 的 jar 文件添加到 web 应用中
- 将 Spring Session filter 添加到 web 应用的配置中
- 配置 Spring Session 如何选择 session 数据存储的连接
Spring Session 自带了对 Redis 的支持。搭建和安装 redis 的细节可以参考该地址。
有两种常见的方式能够完成上述的 Spring Session 配置步骤。第一种方式是使用 Spring Boot 来自动配置 Spring Session。第二种配置 Spring Session 的方式是手动完成上述的每一个配置步骤。
<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session</artifactId> <version>1.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> </dependency>
其中,spring-boot-starter-redis
依赖能够确保使用 redis 所需的所有 jar 都会包含在应用中,所以它们可以借助 Spring Boot 进行自动装配。spring-session 依赖将会引入 Spring Session
的 jar。
至于 Spring Session Servlet filter 的配置,可以通过 Spring Boot 的自动配置来实现,这只需要在 Spring Boot 的配置类上使用 @EnableRedisHttpSession
注解就可以了,如下面的代码片段所示。
@SpringBootApplication @EnableRedisHttpSession public class ExampleApplication { public static void main(String[] args) { SpringApplication.run(ExampleApplication.class, args); } }
至于 Spring Session 到 Redis 连接的配置,可以添加如下配置到 Spring Boot 的 application.properties 文件中:
spring.redis.host=localhost spring.redis.password=secret spring.redis.port=6379
Spring Boot 提供了大量的基础设施用来配置到 Redis 的连接,定义到 Redis 数据库连接的各种方式都可以用在这里。你可以参考该地址的逐步操作指南,来了解如何使用Spring Session 和Spring Boot。
在传统的 web 应用中,可以参考该指南来了解如何通过web.xml 来使用Spring Session。
在传统的 war 文件中,可以参考该指南来了解如何不使用web.xml 进行配置。
默认情况下,Spring Session 会使用 HTTP cookie 来存储 session id,但是我们也可以配置 Spring Session 使用自定义的 HTTP header 信息,如x-auth-token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3
,当构建 REST API 的时候,这种方式是很有用的。完整的指南可以参考该地址。
使用 Spring Session
Spring Session 配置完成之后,我们就可以使用标准的 Servlet API 与之交互了。例如,如下的代码定义了一个 servlet,它使用标准的 Servlet session API 来访问 session。
@WebServlet("/example") public class Example extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 使用正常的 servlet API 获取 session,在底层, // session 是通过 Spring Session 得到的,并且会存储到 Redis 或 // 其他你所选择的数据源中 HttpSession session = request.getSession(); String value = session.getAttribute("someAttribute"); } }
每个浏览器多个 Session
Spring Session 会为每个用户保留多个 session,这是通过使用名为“_s
”的 session 别名参数实现的。例如,如果到达的请求为 http://example.com/doSomething?_s=0 ,那么 Spring Session 将会读取“_s”参数的值,并通过它确定这个请求所使用的是默认 session。
如果到达的请求是 http://example.com/doSomething?_s=1
的话,那么 Spring Session 就能知道这个请求所要使用的 session 别名为 1. 如果请求没有指定“_s
”参数的话,例如 http://example.com/doSomething,那么 Spring Session 将其视为使用默认的 session,也就是说_s=0
。
要为某个浏览器创建新的 session,只需要调用javax.servlet.http.HttpServletRequest.getSession()
就可以了,就像我们通常所做的那样,Spring Session 将会返回正确的 session 或者按照标准 Servlet 规范的语义创建一个新的 session。下面的表格描述了针对同一个浏览器窗口,getSession()
面对不同 url 时的行为。
HTTP 请求 URL |
Session 别名 |
getSession() 的行为 |
example.com/resource |
如果存在 session 与别名 0 关联的话,就返回该 session,否则的话创建一个新的 session 并将其与别名 0 关联。 |
|
example.com/resource?_s=1 |
1 |
如果存在 session 与别名 1 关联的话,就返回该 session,否则的话创建一个新的 session 并将其与别名 1 关联。 |
example.com/resource?_s=0 |
如果存在 session 与别名 0 关联的话,就返回该 session,否则的话创建一个新的 session 并将其与别名 0 关联。 |
|
example.com/resource?_s=abc |
abc |
如果存在 session 与别名 abc 关联的话,就返回该 session,否则的话创建一个新的 session 并将其与别名 abc 关联。 |
如上面的表格所示,session 别名不一定必须是整型,它只需要区别于其他分配给用户的 session 别名就可以了。但是,整型的 session 别名可能是最易于使用的,Spring Session 提供了HttpSessionManager
接口,这个接口包含了一些使用 session 别名的工具方法。
我们可以在HttpServletRequest
中,通过名为“org.springframework.session.web.http.HttpSessionManager”
的属性获取当前的HttpSessionManager
。如下的样例代码阐述了如何得到 HttpSessionManager,并且在样例注释中描述了其关键方法的行为。
@WebServlet("/example") public class Example extends HttpServlet { @Override protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException { /* * 在请求中,根据名为 org.springframework.session.web.http.HttpSessionManager 的 key * 获得 Spring Session session 管理器的引用 */ HttpSessionManager sessionManager=(HttpSessionManager)request.getAttribute( "org.springframework.session.web.http.HttpSessionManager"); /* * 使用 session 管理器找出所请求 session 的别名。 * 默认情况下,session 别名会包含在 url 中,并且请求参数的名称为“_s”。 * 例如,http://localhost:8080/example?_s=1 * 将会使如下的代码打印出“Requested Session Alias is: 1” */ String requestedSessionAlias=sessionManager.getCurrentSessionAlias(request); System.out.println("Requested Session Alias is: " + requestedSessionAlias); /* 返回一个唯一的 session 别名 id,这个别名目前没有被浏览器用来发送请求。 * 这个方法并不会创建新的 session, * 我们需要调用 request.getSession() 来创建新 session。 */ String newSessionAlias = sessionManager.getNewSessionAlias(request); /* 使用新创建的 session 别名来建立 URL,这个 URL 将会包含 * “_s”参数。例如,如果 newSessionAlias 的值为 2 的话, * 那么如下的方法将会返回“/inbox?_s=2” */ String encodedURL = sessionManager.encodeURL("/inbox", newSessionAlias); System.out.println(encodedURL); /* 返回 session 别名与 session id 所组成的 Map, * 它们是由浏览器发送请求所形成的。 */ Map < String, String > sessionIds = sessionManager.getSessionIds(request); } }
结论
Spring Session 为企业级 Java 的 session 管理带来了革新,使得如下的任务变得更加容易:
- 编写可水平扩展的原生云应用。
- 将 session 所保存的状态卸载到特定的外部 session 存储中,如 Redis 或 Apache Geode 中,它们能够以独立于应用服务器的方式提供高质量的集群。
- 当用户使用 WebSocket 发送请求的时候,能够保持 HttpSession 处于活跃状态。
- 在非 Web 请求的处理代码中,能够访问 session 数据,比如在 JMS 消息的处理代码中。
- 支持每个浏览器上使用多个 session,这样就可以很容易地构建更加丰富的终端用户体验。
- 控制客户端和服务器端之间如何进行 session id 的交换,这样更加易于编写 Restful API,因为它可以从 HTTP 头信息中获取 session id,而不必再依赖于 cookie。
如果你想抛弃传统的重量级应用服务器,但受制于已经使用了这些应用服务器的 session 集群特性,那么 Spring Session 将是帮助你迈向更加轻量级容器的重要一步,这些轻量级的容器包括 Tomcat、Jetty 或 Undertow。