SpringBoot--开发实战--SpringSession会
一、Spring-session简介
在传统单机web应用中,一般使用tomcat/jetty等web容器时,用户的session都是由容器管理。浏览器使用cookie中记录sessionId,容器根据sessionId判断用户是否存在会话session。这里的限制是,session存储在web容器中,被单台服务器容器管理。
但是网站主键演变,分布式应用和集群是趋势(提高性能)。此时用户的请求可能被负载分发至不同的服务器,此时传统的web容器管理用户会话session的方式即行不通。除非集群或者分布式web应用能够共享session,尽管tomcat等支持这样做。但是这样存在以下两点问题:
- 需要侵入web容器,提高问题的复杂;
- web容器之间共享session,集群机器之间势必要交互耦合;
基于这些,必须提供新的可靠的集群分布式/集群session的解决方案,突破traditional-session单机限制(即web容器session方式,下面简称traditional-session),spring-session应用而生。
传统模式
传统模式中,当request进入web容器,根据reqest获取session时,如果web容器中存在session则返回,如果不存在,web容器则创建一个session。然后返回response时,将sessonId作为response的head一并返回给客户端或者浏览器。
spring-session模式
spring-session的核心思想在于此:将session从web容器中剥离,存储在独立的存储服务器中。目前支持多种形式的session存储器:Redis、Database、MogonDB等。session的管理责任委托给spring-session承担。当request进入web容器,根据request获取session时,由spring-session负责存存储器中获取session,如果存在则返回,如果不存在则创建并持久化至存储器中。
官网:https://spring.io/projects/spring-session
用nginx做负载的时候,会出现每一次http请求都会分配到不同的tomcat上,这样用session存储用户数据就会有问题。因为tomcat集群之间没有实现session共享。
二、spring-session实现方案
- Servlet获取Session的方式
HttpServletRequest request = ...
HttpSession session = request.getSession(false);
其中HttpServletRequest和HttpSession都是servlet规范中定义的接口,web容器实现的标准。那如果引入》> spring-session,要如何获取session?
1)遵循servlet规范,同样方式获取session,对应用代码无侵入且对于developers透明化
2)全新实现一套session规范,定义一套新的api和session管理机制
两种方案都可以实现,但是显然第一种更友好,且具有兼容性。spring-session正是第一种方案的实现。
实现第一种方案的关键点在于做到透明和兼容;
接口适配:仍然使用HttpServletRequest获取session,获取到的session仍然是HttpSession类型——适配器模式;
类型包装增强:Session不能存储在web容器内,要外化存储——装饰模式;
三、特点与工作原理
spring-session在无需绑定web容器的情况下提供对集群session的支持。并提供对以下情况的透明集成:
工作原理HttpSession:容许替换web容器的HttpSession
WebSocket:使用WebSocket通信时,提供Session的活跃
WebSession:容许以应用中立的方式替换webflux的webSession
spring-session分为以下核心模块:
SessionRepositoryFilter:Servlet规范中Filter的实现,用来切换HttpSession至Spring Session,包装HttpServletRequest和HttpServletResponse
HttpServerletRequest/HttpServletResponse/HttpSessionWrapper包装器:包装原有的HttpServletRequest、HttpServletResponse和Spring Session,实现切换Session和透明继承HttpSession的关键之所在
Session:Spring Session模块
SessionRepository:管理Spring Session的模块
HttpSessionStrategy:映射HttpRequst和HttpResponse到Session的策略
四、Maven依赖
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
</dependency>
或
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis 依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.0</version>
</dependency>
五、application.properties配置
#session
#配合为redis表示开启redis会话共享,none单机
# spirngboot默认就是使用redis方式,也可以写redis
spring.session.store-type=none
spring.session.redis.namespace=evidence
#redis配置
spring.data.redis.repositories.enabled=true
spring.redis.database=0
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.lettuce.pool.max-active=1024
spring.redis.lettuce.pool.min-idle=10
Spring Boot配置很容易切换到不同的Session管理方式,总共有以下几种:
Redis,Session数据存放Redis中。
JDBC,会话数据存放在数据库中,默认情况下SPRINGSESSION表存放Session基本信息,如sessionId、创建时间、最后一次访问时间等,SPRING_SESSION ATTRIBUTES存放了session数据,ATTRIBUTE_NAME列保存了Session的Key,ATTRIBUTE_BYTES列以字节形式保存了Session的Value,Spring Session会自动创建这两张表。
Hazelcast,Session数据存放到Hazelcast。
None,禁用Spring Session功能。
六、创建控制器
@Controller
@RequestMapping(value = "/")
public class IndexController {
@ResponseBody
@GetMapping(value = "/session")
public Map<String, Object> getSession(HttpServletRequest request) {
// 添加数据到Session
request.getSession().setAttribute("username", "admin");
// 添加sessionID到Map
Map<String, Object> map = new HashMap<>();
map.put("sessionId", request.getSession().getId());
return map;
}
@ResponseBody
@GetMapping(value = "/get")
public String get(HttpServletRequest request) {
// 获取Session数据
String userName = (String) request.getSession().getAttribute("username");
return userName;
}
@ResponseBody
@GetMapping(value = "/logout")
public String logout(HttpServletRequest request) {
// 销毁sessioin
request.getSession().invalidate();
return "ok";
}
}
七、启动类配置
@SpringBootApplication
@EnableRedisHttpSession // 启用ResisSession存储
@EnableCaching // 启用缓存
public class SpringSessionApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSessionApplication.class, args);
}
}
八、测试
创建sessioin数据 获取session数据 redis查看九、Session监听器
- 创建监听配置类
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 120)
@Slf4j
public class SpringSessionConfiguration {
/**
* Redis内session过期事件监听
* @param expiredEvent
*/
@EventListener
public void onSessionExpired(SessionExpiredEvent expiredEvent) {
String sessionId = expiredEvent.getSessionId();
log.info("Session失效事件:"+sessionId);
}
/**
* Redis内session创建事件监听
* @param createdEvent
*/
@EventListener
public void onSessionCreated(SessionCreatedEvent createdEvent) {
String sessionId = createdEvent.getSessionId();
log.info("创建Session事件:"+sessionId);
}
/**
* Redis内session删除事件监听
* @param deletedEvent
*/
@EventListener
public void onSessionDeleted(SessionDeletedEvent deletedEvent) {
String sessionId = deletedEvent.getSessionId();
log.info("删除Session事件:"+sessionId);
}
}
- 测试结果
请求地址分别为:
http://localhost:9000/session
http://localhost:9000/logout
测试结果
十、常见问题
- 使用@WebListener不能监听?
正常,经测试,如果使用SpringSessioin必须使用配置方式。