Spring Session provides an API and implementations for managing a user’s session information.
Spring Session提供了一种用于管理用户session信息管理的API。
Spring Session特点
传统的Servlet应用中,Session是存储在服务端的,即:Session信息与特定的Web服务相互绑定。Spring Session提供了一种支持分布式session的解决方案,而无需与特定应用容器相绑定。
- HttpSession:支持以应用容器中立的方式替换HttpSession,支持在Http请求头中提供session IDs,以便与Restful API配合使用。
- WebSocket:提供了WebSocket消息通信中保持HttpSession会话的能力。
- WebSession:允许以应用容器中立的方式替换 Spring WebFlux 的 WebSession。
核心模块
Spring Session由以下模块组成,
- Spring Session Core:提供Spring Session 核心功能和 API
- Spring Session Data Redis:提供由 Redis 支持的 SessionRepository 和 ReactiveSessionRepository 实现,以及配置支持
- Spring Session JDBC:提供由关系型数据库支持的SessionRepository实现,以及配置支持
- Spring Session Hazelcast:提供由Hazelcast支持的SessionRepository实现,以及配置支持
- Spring Session MongoDB:提供由MongoDB支持的SessionRepository实现,以及配置支持
Spring Session解决的问题
当用户和web应用交互时,服务端创建一个Session去追踪用户的操作。这个Session可以存储与用户相关的信息,例如:用户行为,登录状态,购物车内容等等。但是,Session在集群环境中存在问题,因为session被存储在服务内存中。
如上如所示,假定:Spring App#2接收到了session #3,该服务端应用无法获取到session数据,因为Spring App#1和Spring App#2的session信息是独立存储在各自的应用内存中的。这对集群环境是一个问题。
为了解决这个问题,我们必须想办法实现session共享,
如上图所示,通过session共享,可以使得每个应用都能访问到session存储信息。Spring Session正是在应用和session管理之间提供了一个抽象层,将session数据存储在各种持久化存储中,例如:关系型/非关系型数据库中。
通过SpringSession,你可以使用相同的API去管理session,而无需例会session是如何进行持久化存储的,同时也可以无痛的切换持久化存储方案且无需改动任何代码。
此外,SpringSession也提供了session过期和不同web应用之间的跨上下文通信机制。
Spring Session简化了web应用的session管理,使得开发者可以专注于构建应用程序的核心功能。
Spring Session的应用场景
以下是Spring Session的一些应用案例:
- 分布式web应用:如果网络应用程序分布在多个服务器上,那么管理用户会话可能会很困难。Spring Session 可以将会话数据存储在共享数据库或 Redis 中,让所有服务器都能访问和更新会话数据。
- 会话拓展能力:在用户并发量较高的大型web应用中,将会话信息存储在服务器内存中会导致可拓展性问题。Spring Session 可以将会话数据存储在持久化存储中,从而提高可扩展性并降低内存溢出的风险。
- session备份和恢复:将会话数据存储在持久性存储中,还能在服务器出现故障或停机时提供备份和恢复会话数据的机制。
Quick Start
官网文档:Samples and Guides (Start Here) :: Spring Session。
以下使用Spring Session Data Redis核心模块,实现一个Spring Session快速入门案例。
引入依赖
<!--spring-session-data-redis-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!--spring-boot-starter-data-redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
可以看到Spring session data redis内置了spring data redis依赖,
配置session存储类型
在application.proeprties文件中,配置session数据存储类型为Redis,
#session store type
spring.session.store-type=redis
相当于为SpringBoot应用添加了@EnableRedisHttpSession注解,
得益于SpringBoot autoConfigure自动装配的特点,我们可以进行少量的配置即可。此处我们可以去查看一下spring session自动装配的类为SessionAutoConfiguration:
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,
点进去看一下自动装配生效的条件是:Session接口存在,
我们已经引入了Spring Session相关的依赖,那么Session接口自然是存在的。
其它配置
此外,我们还可以配置如下信息,会话过期时间、Session刷新模式、redis-session命名空间,
server.port=8080
#session store type
spring.session.store-type=redis
# Session timeout(单位为秒s). If a duration suffix is not specified, seconds is used.(默认为30min)
server.servlet.session.timeout=3600
# Sessions flush mode.(默认为ON_SAVE)
spring.session.redis.flush-mode=on_save
# Namespace for keys used to store sessions.(默认为spring:session)
spring.session.redis.namespace=spring:session
Redis连接配置
Spring Boot自动创建了RedisConnectionFactory
用于将spring Session连接到端口为6379的Redis Server。在生产环境中,建议更新Redis服务的配置端点信息。
# redis
spring.redis.host=localhost
spring.redis.password=
spring.redis.port=6379
注解:启用Redis-Session存储
在启动类上使用@EnableRedisHttpSession注解,其作用是:用于创建一个名为 springSessionRepositoryFilter 的 Spring Bean,它实现了Filter过滤器。该过滤器负责替换 HttpSession 实现,使其由 Spring Session 支持。简单讲:就是
@SpringBootApplication
@EnableRedisHttpSession
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
案例测试
以上步骤完成之后,启动SpringBoot应用成功,其实就已经整合完成了。但是却感受不到任何效果。以下我们从一个案例感受一下Session存储用户信息的作用。
案例描述
创建两个接口:
①app/addUser?id=XX:创建一个id为指定XX值的用户,并将用户信息存储到Session中
②/app/getUser?id=XX:用于从Session中获取指定id的用户信息
SysUser类
package com.example.demo.model.entity;
import java.io.Serializable;
public class SysUser implements Serializable {
private static final long serialVersionUID = -1802561599630491376L;
private Integer id;
private String name;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public SysUser() {
}
public SysUser(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "SysUser{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
创建接口
package com.example.demo.web;
import com.example.demo.model.entity.SysUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
@Slf4j
@RestController
@RequestMapping(value = "/app")
public class AppController {
//接收一个id,创建user并返回
@GetMapping("/addUser")
public SysUser addUser(HttpSession session,
@RequestParam(value = "id")Integer id){
SysUser user = new SysUser(id, "张三", 18);
//设置user信息到session中
session.setAttribute(String.valueOf(id),user);
log.info("create SysUser:{}",user);
return user;
}
//接收一个id,获取Session中存储的user信息
@GetMapping("/getUser")
public SysUser getUser(HttpSession session,
@RequestParam(value = "id")Integer id){
SysUser user = (SysUser) session.getAttribute(String.valueOf(id));
log.info("get SysUser:{}",user);
return user;
}
}
测试接口
(1)app/addUser?id=1,创建一个用户,
此时查看redis数据库,会看到对应的信息,
(2)获取用户信息:/app/getUser?id=1,
查看请求头和响应头中,会看到cookie中已经产生了SESSION键,
分布式案例测试
以上只是单体应用,我们的访问接口都是8080,我们重新启动一个8001的web应用,然后访问getUser接口,看看能不能访问到session中存储的用户信息。
启动8001端口的应用,访问接口,可看到成功访问到用户信息,
另外,我们对比Cookie中的SESSION值,也是相同的,
加入我们更换浏览器访问呢?可以看到SESSION对应的值变了,接口响应200正常,但是返回接口为null,找不到对应的用户信息,这和我们的预期是一致的。
此时查看Redis数据库,会发现多了一条SESSION会话信息,