一、背景

笔者在讨论组中看到网友提到的 Session 共享问题,因此私下查阅文档,最终锁定使用 Spring Session 解决问题并在此记录。

二、介绍

Spring Session 提供了一套 API 来管理用户的会话信息。

使用 Spring Session 可以替换项目容器中 HttpSession 的实现。此外, Spring Session 还提供如下功能:

  1. Session 集群:Spring Session 使得支持集群会话变得非常简单。使用 Spring Session 我们不必为 session 的集群而将项目绑定在指定的应用程序中。

  2. 多浏览器会话:Spring Session 提供了在单个浏览器中管理多个 session 的方案。

  3. RESTful APIs:Spring Session 允许在请求头中添加 Session ID 以实现 RESTful 风格的 API。

本篇内容之针对第一点的功能进行介绍。

原理图如下:

浏览器发起请求后,tomcat (由负载均衡分配指向具体的 tomcat)获取请求中的 session id 在 session 存储容器中找到相应的 Session 对象。

针对 Session 存储的容器, Spring Session 提供了很多方案:

  1. HttpSession with Redis

  2. HttpSession with Pivotal GemFire

  3. HttpSession with JDBC

  4. HttpSession with Mongo

  5. HttpSession with Hazelcast

三、实现

JDK:1.8
容器:Tomcat 8
Session 存储容器:Redis 3.2.0

本次测试使用 HttpSession with Redis 方案, 为了测试方便,笔者将项目部署到同一台虚拟机上的 2 个 tomcat 中,使用 8080 和 8081 端口启动。

# 3.1 添加依赖

  1. <dependency>
  2. <groupId>org.springframework.data</groupId>
  3. <artifactId>spring-data-redis</artifactId>
  4. <version>1.8.9.RELEASE</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>redis.clients</groupId>
  8. <artifactId>jedis</artifactId>
  9. <version>2.9.0</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework.session</groupId>
  13. <artifactId>spring-session</artifactId>
  14. <version>1.3.1.RELEASE</version>
  15. </dependency>

此处省略 Spring 等相关 jar 。

# 3.2 applicationContext-session.xml 文件

  1. <context:annotation-config/>
  2. <!-- redis 连接池 -->
  3. <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
  4. <property name="maxTotal" value="20"></property>
  5. <property name="maxIdle" value="1"></property>
  6. </bean>
  7. <!-- redis 连接工厂 -->
  8. <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
  9. destroy-method="destroy">
  10. <property name="hostName" value="192.168.2.11"/>
  11. <property name="port" value="6379"/>
  12. <property name="timeout" value="5000"/>
  13. <property name="password" value=""/>
  14. <property name="usePool" value="true"/>
  15. <property name="poolConfig" ref="jedisPoolConfig"/>
  16. </bean>
  17. <!-- spring session 配置 -->
  18. <bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
  19. <property name="maxInactiveIntervalInSeconds" value="600"/>
  20. </bean>

当项目启动时,Spring 会创建一个名为 springSessionRepositoryFilter 的 Bean(过滤器),而 Bean 负责将 HttpSession 的实现替换成 Spring Session 。Spring Session 依赖 Redis 存储客户端的 Session 信息。

# 3.3 web.xml 文件

  1. <filter>
  2. <filter-name>springSessionRepositoryFilter</filter-name>
  3. <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  4. </filter>
  5. <filter-mapping>
  6. <filter-name>springSessionRepositoryFilter</filter-name>
  7. <url-pattern>/*</url-pattern>
  8. </filter-mapping>

每个客户端请求经过 DelegatingFilterProxy 后,DelegatingFilterProxy 会在 Spring 容器中查找名为 springSessionRepositoryFilter 的 Bean,将其转化成 Filter 对请求数据进行过滤。

注意:如果项目中还配置了其他过滤器,该过滤器必须配置在所有过滤器的首位。

# 3.4 后端代码

  1. @Controller
  2. public class LoginController {
  3. @RequestMapping("login")
  4. public String login(String userName, String password,HttpServletRequest request) {
  5. // 首次登录
  6. if ("admin".equals(userName) && "admin".equals(password)) {
  7. HttpSession session = request.getSession();
  8. session.setAttribute("userName", userName);
  9. return "manageUI";
  10. }
  11. // 如果已经登录过,从另一个 tomcat 访问该方法,跳转到 manageUI 页面可以查看 session 信息
  12. if ("".equals(userName) && "".equals(password)) {
  13. return "manageUI";
  14. }
  15. return "redirect:/index.jsp";
  16. }
  17. @RequestMapping("logout")
  18. public String logout(HttpSession session) {
  19. session.removeAttribute("userName");
  20. session.removeAttribute("url");
  21. return "redirect:/index.jsp";
  22. }
  23. }

# 3.5 前端代码

index.jsp 页面

  1. <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
  2. <!DOCTYPE html>
  3. <html lang="zh">
  4. <head>
  5. <meta charset="utf-8">
  6. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  7. <meta name="viewport" content="width=device-width, initial-scale=1">
  8. <meta name="description" content="">
  9. <meta name="author" content="">
  10. <title>登陆界面</title>
  11. <link href="/resources/css/bootstrap.min.css" rel="stylesheet">
  12. <style>
  13. html {
  14. background: url("/resources/images/bg.png") no-repeat center center;
  15. }
  16. label {
  17. color: #fff;
  18. }
  19. .container {
  20. position:absolute;
  21. top:50%;
  22. left:50%;
  23. margin-top: -115px;
  24. margin-left: -250px;
  25. width: 500px;
  26. height:230px;
  27. padding:50px;
  28. border: 2px solid #eee;
  29. border-radius: 5px;
  30. box-shadow:5px 5px 16px #000;
  31. }
  32. </style>
  33. </head>
  34. <body>
  35. <div class="container">
  36. <form class="form-horizontal" role="form" action="/login" method="post">
  37. <div class="form-group">
  38. <label for="inputEmail3" class="col-sm-2 control-label">用户名</label>
  39. <div class="col-sm-10">
  40. <input type="text" class="form-control" name="userName" placeholder="用户名">
  41. </div>
  42. </div>
  43. <div class="form-group">
  44. <label for="inputPassword3" class="col-sm-2 control-label">密码</label>
  45. <div class="col-sm-10">
  46. <input type="password" class="form-control" name="password" placeholder="密码">
  47. </div>
  48. </div>
  49. <div class="form-group">
  50. <div class="col-sm-offset-2 col-sm-10">
  51. <button type="submit" class="btn btn-primary" style="width: 100%">登陆</button>
  52. </div>
  53. </div>
  54. </form>
  55. </div>
  56. </body>
  57. </html>

manageUI.jsp 页面

  1. <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
  2. <!DOCTYPE html>
  3. <html lang="zh">
  4. <head>
  5. <meta charset="utf-8">
  6. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  7. <meta name="viewport" content="width=device-width, initial-scale=1">
  8. <meta name="description" content="">
  9. <meta name="author" content="">
  10. <title>管理界面</title>
  11. <link href="/resources/css/bootstrap.min.css" rel="stylesheet">
  12. </head>
  13. <body>
  14. <div class="container">
  15. <div class="jumbotron">
  16. <h3>端口为 8080 的页面</h3>
  17. <h3>用户名:${sessionScope.userName}(session 域数据)</h3>
  18. <p><a class="btn btn-lg btn-success" href="/logout" role="button">注销</a></p>
  19. </div>
  20. </div>
  21. </body>
  22. </html>

注意:8081 项目的页面需要改成 “端口为 8081 的页面”。

四、演示

预期效果:

  1. 首先访问 8080 端口的项目并进行登陆操作,跳转到管理界面并显示保存的信息。

  2. 在同个浏览器中访问 8081 端口项目的页面,不需要输入账号密码直接点击登陆按钮,会直接跳转到管理界面。如果 session 实现了共享,那么在管理界面就可以查看由 8080 端口项目保存在 session 的信息。否则反之。

演示图如下:

使用 Spring Session 确实很方便和简单,我们不需要额外的学习 API,只需配置就能实现功能。

五、参考文档

六、源码下载

session-share