为什么需要 Session 共享?
我们理解一下为什么要做这件事。
- 问题场景:当你的 Java Web 应用(如 Spring Boot + Tomcat)部署在多台服务器上时,通常会使用负载均衡器(如 Nginx, F5)将用户请求分发到不同的服务器。
- 产生的问题:用户的第一次请求可能被分发到 Server A,Session 信息被创建并存储在 Server A 的内存中,用户的下一次请求可能被分发到 Server B,但 Server B 并没有用户的 Session 信息,导致用户需要重新登录,或者丢失之前的操作状态。
- 解决方案:Session 共享,我们将 Session 数据从各个服务器的本地内存中,统一存储到一个所有服务器都能访问的外部存储中,当任何一台服务器需要获取 Session 数据时,都从这个外部存储中读取和写入,Redis 因为其高性能、丰富的数据结构,是实现 Session 共享最主流的选择。
实现原理
实现 Session 共享的核心思想是 将 Session 的创建、读取、更新和删除操作从 Web 容器(如 Tomcat)重定向到 Redis。
- 拦截请求:当用户第一次访问应用时,一个过滤器会拦截这个请求。
- 创建/获取 Session ID:如果请求中不包含有效的 Session ID,过滤器会创建一个新的 Session ID(通常是
JSESSIONID)。 - 操作 Redis:
- 创建 Session:过滤器将新的 Session ID 作为 key,将 Session 对象序列化后的数据作为 value,存入 Redis 中,并设置一个过期时间(与 Session 超时时间一致)。
- 读取 Session:如果请求中包含 Session ID,过滤器会根据这个 ID 去 Redis 中查找对应的 Session 数据,如果找到,就将其反序列化并设置为当前请求的 Session 对象。
- 后续处理:之后,应用代码中所有的
request.getSession()操作,实际上都是操作这个从 Redis 中加载的 Session 对象。 - Session 失效:当 Session 超时或调用
invalidate()方法时,对应的 key-value 对会从 Redis 中被删除。
如何实现?(主流方案)
主要有两种实现方式:手动编码和 使用 Spring Session。
手动编码(不推荐,但有助于理解原理)
这种方式需要你自己处理所有与 Redis 交互的逻辑,代码量多且容易出错,通常只在学习或特殊需求下使用。

核心步骤:
-
添加依赖:
<!-- Redis 客户端 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- 序列化工具,如 Jackson --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> -
配置 Redis:
# application.properties spring.redis.host=your-redis-host spring.redis.port=6379
-
创建过滤器: 创建一个
HttpSessionListener或Filter,在doFilter方法中实现核心逻辑。
// 伪代码示例 public class RedisSessionFilter implements Filter { private RedisTemplate<String, Object> redisTemplate; @Override public void init(FilterConfig filterConfig) { // 初始化 RedisTemplate } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; // 1. 从请求中获取 sessionId String sessionId = getSessionIdFromCookie(httpRequest); // 2. 如果没有 sessionId,生成一个新的 if (sessionId == null) { sessionId = generateSessionId(); // 将 sessionId 写入 Cookie addSessionIdToCookie(httpResponse, sessionId); } // 3. 从 Redis 中获取 Session 数据 Map<String, Object> sessionData = (Map<String, Object>) redisTemplate.opsForValue().get("session:" + sessionId); HttpSessionWrapper sessionWrapper = null; if (sessionData == null) { // Redis 中没有,创建一个新的 HttpSessionWrapper sessionWrapper = new HttpSessionWrapper(httpRequest.getSession(false)); } else { // 如果有,创建一个包装的 HttpSession,数据从 Redis 来 sessionWrapper = new HttpSessionWrapper(httpRequest.getSession(true)); // 将数据填充到 sessionWrapper 中... } // 4. 将包装后的 Session 绑定到请求上 httpRequest = new SessionRequestWrapper(httpRequest, sessionWrapper); chain.doFilter(httpRequest, response); } // ... 其他辅助方法 ... }注意:你需要自己处理 Session 的序列化/反序列化,以及 Session 对象的包装,非常繁琐。
使用 Spring Session(强烈推荐)
Spring Session 是 Spring 家族的一个项目,它提供了一种简单的方式来管理 HTTP 会话,你几乎不需要写任何代码,只需要做一些配置,就能自动完成 Session 共享。
核心优势:
- 零侵入:你原有的业务代码(
request.getSession())完全不需要修改。 - 简单易用:只需要添加依赖和配置即可。
- 功能强大:支持多种数据存储(Redis, JDBC, MongoDB 等),支持 WebSocket、Session 事件等。
实现步骤(以 Spring Boot + Redis 为例):

添加依赖
在你的 pom.xml 中添加 spring-boot-starter-data-redis 和 spring-session-data-redis。
<dependencies>
<!-- Spring Boot Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Spring Session Data Redis (核心) -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
</dependencies>
配置 Redis 和 Spring Session
在 application.properties 或 application.yml 中进行配置。
application.properties 示例:
# Redis 配置 spring.redis.host=localhost spring.redis.port=6379 # Spring Session 配置 # 这个注解会由 @EnableRedisHttpSession 替代,这里只是展示概念 # spring.session.store-type=redis # 设置 Session 过期时间,单位秒,默认 30 分钟 server.servlet.session.timeout=1800
application.yml 示例 (推荐):
spring:
redis:
host: localhost
port: 6379
session:
store-type: redis # 关键配置:指定使用 Redis 作为 Session 存储
timeout: 1800s # Session 超时时间
# 如果需要自定义 Redis 中 Session 的 key 前缀
# redis:
# namespace: myapp:sessions
启用 Spring Session
在你的 Spring Boot 启动类上添加 @EnableRedisHttpSession 注解,这个注解是整个配置的核心,它会自动为你配置好所有必要的 Bean(包括上面提到的过滤器)。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@SpringBootApplication
// 这个注解会启用 Redis-backed HTTP Session
// maxInactiveInSeconds: 设置 Session 的最大不活动时间(秒),会覆盖 application.yml 中的配置
@EnableRedisHttpSession(maxInactiveInSeconds = 1800)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
验证
你的应用已经配置完成!你可以编写一个简单的 Controller 来测试。
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
import java.util.UUID;
@RestController
public class SessionController {
@GetMapping("/set-session")
public String setSession(HttpSession session) {
// 从 Session 中获取一个属性,如果没有则创建
String userId = (String) session.getAttribute("userId");
if (userId == null) {
userId = UUID.randomUUID().toString();
session.setAttribute("userId", userId);
}
return "Session attribute 'userId' has been set to: " + userId;
}
@GetMapping("/get-session")
public String getSession(HttpSession session) {
String userId = (String) session.getAttribute("userId");
if (userId == null) {
return "No 'userId' found in session.";
}
return "Session attribute 'userId'
