博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【源码在文末】SpringSession实战使用(基于SpringBoot项目)
阅读量:2197 次
发布时间:2019-05-02

本文共 11591 字,大约阅读时间需要 38 分钟。

作者:怀瑾握瑜

来源链接:

https://www.cnblogs.com/lxyit/p/9720159.html

spring-boot 整合 spring-session 的自动配置可谓是开箱即用,极其简洁和方便。这篇文章即介绍 spring-boot 整合 spring-session,这里只介绍基于 RedisSession 的实战。

考虑到 RedisSession 模块与 spring-session v2.0.6 版本的差异很小,且能够与 spring-boot v2.0.0 兼容,所以实战篇是基于 spring-boot v2.0.0 基础上配置 spring-session。

配置 spring-session

引入 spring-session 的 pom 配置,由于 spring-boot 包含 spring-session 的 starter 模块,所以 pom 中依赖:

 
org.springframework.session
 
spring-session-data-redis

编写 spring boot 启动类 SessionExampleApplication

/** * 启动类 * * @author huaijin */@SpringBootApplicationpublic class SessionExampleApplication {    public static void main(String[] args) {        SpringApplication.run(SessionExampleApplication.class, args);    }}

配置 application.yml

spring:  session:    redis:      flush-mode: on_save      namespace: session.example      cleanup-cron: 0 * * * * *    store-type: redis    timeout: 1800  redis:    host: localhost    port: 6379    jedis:      pool:        max-active: 100        max-wait: 10        max-idle: 10        min-idle: 10    database: 0

编写 controller

编写登录控制器,登录时创建 session,并将当前登录用户存储 sesion 中。登出时,使 session 失效。

/** * 登录控制器 * * @author huaijin */@RestControllerpublic class LoginController {    private static final String CURRENT_USER = "currentUser";    /**     * 登录     *     * @param loginVo 登录信息     *     * @author huaijin     */    @PostMapping("/login.do")    public String login(@RequestBody LoginVo loginVo, HttpServletRequest request) {        UserVo userVo = UserVo.builder().userName(loginVo.getUserName())                .userPassword(loginVo.getUserPassword()).build();        HttpSession session = request.getSession();        session.setAttribute(CURRENT_USER, userVo);        System.out.println("create session, sessionId is:" + session.getId());        return "ok";    }    /**     * 登出     *     * @author huaijin     */    @PostMapping("/logout.do")    public String logout(HttpServletRequest request) {        HttpSession session = request.getSession(false);        session.invalidate();        return "ok";    }}

编写查询控制器,在登录创建 session 后,使用将 sessionId 置于 cookie 中访问。如果没有 session 将返回错误。

/** * 查询 * * @author huaijin */@RestController@RequestMapping("/session")public class QuerySessionController {    @GetMapping("/query.do")    public String querySessionId(HttpServletRequest request) {        HttpSession session = request.getSession(false);        if (session == null) {            return "error";        }        System.out.println("current's user is:" + session.getId() +  "in session");        return "ok";    }}

编写 Session 删除事件监听器

Session 删除事件监听器用于监听登出时使 session 失效的事件源。

/** * session事件监听器 * * @author huaijin */@Componentpublic class SessionEventListener implements ApplicationListener
 {    private static final String CURRENT_USER = "currentUser";    @Override    public void onApplicationEvent(SessionDeletedEvent event) {        Session session = event.getSession();        UserVo userVo = session.getAttribute(CURRENT_USER);        System.out.println("invalid session's user:" + userVo.toString());    }}

验证测试

编写 spring-boot 测试类,测试 controller,验证 spring-session 是否生效。

/** * 测试Spring-Session: * 1.登录时创建session * 2.使用sessionId能正常访问 * 3.session过期销毁,能够监听销毁事件 * * @author huaijin */@RunWith(SpringRunner.class)@SpringBootTest@AutoConfigureMockMvcpublic class SpringSessionTest {    @Autowired    private MockMvc mockMvc;    @Test    public void testLogin() throws Exception {        LoginVo loginVo = new LoginVo();        loginVo.setUserName("admin");        loginVo.setUserPassword("admin@123");        String content = JSON.toJSONString(loginVo);        // mock登录        ResultActions actions = this.mockMvc.perform(post("/login.do")                .content(content).contentType(MediaType.APPLICATION_JSON))                .andExpect(status().isOk()).andExpect(content().string("ok"));        String sessionId = actions.andReturn()                .getResponse().getCookie("SESSION").getValue();        // 使用登录的sessionId mock查询        this.mockMvc.perform(get("/session/query.do")                .cookie(new Cookie("SESSION", sessionId)))                .andExpect(status().isOk()).andExpect(content().string("ok"));        // mock登出        this.mockMvc.perform(post("/logout.do")                .cookie(new Cookie("SESSION", sessionId)))                .andExpect(status().isOk()).andExpect(content().string("ok"));    }}

测试类执行结果:

create session, sessionId is:429cb0d3-698a-475a-b3f1-09422acf2e9ccurrent's user is:429cb0d3-698a-475a-b3f1-09422acf2e9cin sessioninvalid session's user:UserVo{userName='admin', userPassword='admin@123'

登录时创建 Session,存储当前登录用户。然后在以登录响应返回的 SessionId 查询用户。最后再登出使 Session 过期。

spring-boot 整合 spring-session 自动配置原理

前两篇文章介绍 spring-session 原理时,总结 spring-session 的核心模块。这节中探索 spring-boot 中自动配置如何初始化 spring-session 的各个核心模块。

spring-boot-autoconfigure 模块中包含了 spinrg-session 的自动配置。包 org.springframework.boot.autoconfigure.session 中包含了 spring-session 的所有自动配置项。

其中 RedisSession 的核心配置项是 RedisHttpSessionConfiguration 类。

@Configuration@ConditionalOnClass({ RedisTemplate.class, RedisOperationsSessionRepository.class })@ConditionalOnMissingBean(SessionRepository.class)@ConditionalOnBean(RedisConnectionFactory.class)@Conditional(ServletSessionCondition.class)@EnableConfigurationProperties(RedisSessionProperties.class)class RedisSessionConfiguration { @Configuration public static class SpringBootRedisHttpSessionConfiguration   extends RedisHttpSessionConfiguration {  // 加载application.yml或者application.properties中自定义的配置项:  // 命名空间:用于作为session redis key的一部分  // flushmode:session写入redis的模式  // 定时任务时间:即访问redis过期键的定时任务的cron表达式  @Autowired  public void customize(SessionProperties sessionProperties,    RedisSessionProperties redisSessionProperties) {   Duration timeout = sessionProperties.getTimeout();   if (timeout != null) {    setMaxInactiveIntervalInSeconds((int) timeout.getSeconds());   }   setRedisNamespace(redisSessionProperties.getNamespace());   setRedisFlushMode(redisSessionProperties.getFlushMode());   setCleanupCron(redisSessionProperties.getCleanupCron());  } }}

RedisSessionConfiguration 配置类中嵌套 SpringBootRedisHttpSessionConfiguration 继承了 RedisHttpSessionConfiguration 配置类。首先看下该配置类持有的成员。

@Configuration@EnableSchedulingpublic class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration  implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware,  SchedulingConfigurer { // 默认的cron表达式,application.yml可以自定义配置 static final String DEFAULT_CLEANUP_CRON = "0 * * * * *"; // session的有效最大时间间隔, application.yml可以自定义配置 private Integer maxInactiveIntervalInSeconds = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS; // session在redis中的命名空间,主要为了区分session,application.yml可以自定义配置 private String redisNamespace = RedisOperationsSessionRepository.DEFAULT_NAMESPACE; // session写入Redis的模式,application.yml可以自定义配置 private RedisFlushMode redisFlushMode = RedisFlushMode.ON_SAVE; // 访问过期Session集合的定时任务的定时时间,默认是每整分运行任务 private String cleanupCron = DEFAULT_CLEANUP_CRON; private ConfigureRedisAction configureRedisAction = new ConfigureNotifyKeyspaceEventsAction(); // spring-data-redis的redis连接工厂 private RedisConnectionFactory redisConnectionFactory; // spring-data-redis的RedisSerializer,用于序列化session中存储的attributes private RedisSerializer defaultRedisSerializer; // session时间发布者,默认注入的是AppliationContext实例 private ApplicationEventPublisher applicationEventPublisher; // 访问过期session键的定时任务的调度器 private Executor redisTaskExecutor; private Executor redisSubscriptionExecutor; private ClassLoader classLoader; private StringValueResolver embeddedValueResolver;}

该配置类中初始化了 RedisSession 的最为核心模块之一 RedisOperationsSessionRepository。

@Beanpublic RedisOperationsSessionRepository sessionRepository() { // 创建RedisOperationsSessionRepository RedisTemplate
 redisTemplate = createRedisTemplate(); RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(   redisTemplate); // 设置Session Event发布者。如果对此迷惑,传送门:https://www.cnblogs.com/lxyit/p/9719542.html sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher); if (this.defaultRedisSerializer != null) {  sessionRepository.setDefaultSerializer(this.defaultRedisSerializer); } // 设置默认的Session最大有效期间隔 sessionRepository   .setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds); // 设置命名空间 if (StringUtils.hasText(this.redisNamespace)) {  sessionRepository.setRedisKeyNamespace(this.redisNamespace); } // 设置写redis的模式 sessionRepository.setRedisFlushMode(this.redisFlushMode); return sessionRepository;}

同时也初始化了 Session 事件监听器 MessageListener 模块

@Beanpublic RedisMessageListenerContainer redisMessageListenerContainer() { // 创建MessageListener容器,这属于spring-data-redis范畴,略过 RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(this.redisConnectionFactory); if (this.redisTaskExecutor != null) {  container.setTaskExecutor(this.redisTaskExecutor); } if (this.redisSubscriptionExecutor != null) {  container.setSubscriptionExecutor(this.redisSubscriptionExecutor); } // 模式订阅redis的__keyevent@*:expired和__keyevent@*:del通道, // 获取redis的键过期和删除事件通知 container.addMessageListener(sessionRepository(),   Arrays.asList(new PatternTopic("__keyevent@*:del"),     new PatternTopic("__keyevent@*:expired"))); // 模式订阅redis的${namespace}:event:created:*通道,当该向该通道发布消息, // 则MessageListener消费消息并处理 container.addMessageListener(sessionRepository(),   Collections.singletonList(new PatternTopic(     sessionRepository().getSessionCreatedChannelPrefix() + "*"))); return container;}

上篇文章中介绍到的 spring-session event 事件原理,spring-session 在启动时监听 Redis 的 channel,使用 Redis 的键空间通知处理 Session 的删除和过期事件和使用 Pub/Sub 模式处理 Session 创建事件。

关于 RedisSession 的存储管理部分已经初始化,但是 spring-session 的另一个基础设施模块 SessionRepositoryFilter 是在 RedisHttpSessionConfiguration 父类 SpringHttpSessionConfiguration 中初始化。

@Beanpublic 
 SessionRepositoryFilter
 springSessionRepositoryFilter(  SessionRepository
 sessionRepository) { SessionRepositoryFilter sessionRepositoryFilter = new SessionRepositoryFilter<>(   sessionRepository); sessionRepositoryFilter.setServletContext(this.servletContext); sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver); return sessionRepositoryFilter;}

spring-boot 整合 spring-session 配置的层次:

RedisSessionConfiguration |_ _ SpringBootRedisHttpSessionConfiguration   |_ _ RedisHttpSessionConfiguration     |_ _ SpringHttpSessionConfiguration

回顾思考 spring-boot 自动配置 spring-session,非常合理。

  • SpringHttpSessionConfiguration 是 spring-session 本身的配置类,与 spring-boot 无关,毕竟 spring-session 也可以整合单纯的 spring 项目,只需要使用该 spring-session 的配置类即可。

  • RedisHttpSessionConfiguration 用于配置 spring-session 的 Redission,毕竟 spring-session 还支持其他的各种 session:Map/JDBC/MogonDB 等,将其从 SpringHttpSessionConfiguration 隔离开来,遵循开闭原则和接口隔离原则。但是其必须依赖基础的 SpringHttpSessionConfiguration,所以使用了继承。RedisHttpSessionConfiguration 是 spring-session 和 spring-data-redis 整合配置,需要依赖 spring-data-redis。

  • SpringBootRedisHttpSessionConfiguration 才是 spring-boot 中关键配置

  • RedisSessionConfiguration 主要用于处理自定义配置,将 application.yml 或者 application.properties 的配置载入。

Tips:

配置类也有相当强的设计模式。遵循开闭原则:对修改关闭,对扩展开放。遵循接口隔离原则:变化的就要单独分离,使用不同的接口隔离。SpringHttpSessionConfiguration 和 RedisHttpSessionConfiguration 的设计深深体现这两大原则。

参考

Spring Session参考文:https://spring.io/projects/spring-session#samples

本文例子源码

https://github.com/lixyou/spring-boot-example/tree/master/session-example

更多精彩推荐☞ 外包程序员入职蚂蚁金服被质疑,网友:人生污点☞ 前后端分离三连问:为何分离?如何分离?分离后的接口规范?☞ 如何设计一个通用的权限管理系统☞ 去一家小公司从0到1搭建后端架构,做个总结!☞ 这应该是全网最全的Git分支开发规范手册~
最后,推荐给大家一个有趣有料的公众号:写代码的渣渣鹏,7年老程序员教你写bug,回复 面试或资源 送一你整套开发笔记 有惊喜哦
你可能感兴趣的文章
C++/C 宏定义(define)中# ## 的含义 宏拼接
查看>>
Git安装配置
查看>>
linux中fork()函数详解
查看>>
C语言字符、字符串操作偏僻函数总结
查看>>
Git的Patch功能
查看>>
分析C语言的声明
查看>>
TCP为什么是三次握手,为什么不是两次或者四次 && TCP四次挥手
查看>>
C结构体、C++结构体、C++类的区别
查看>>
进程和线程的概念、区别和联系
查看>>
CMake 入门实战
查看>>
绑定CPU逻辑核心的利器——taskset
查看>>
Linux下perf性能测试火焰图只显示函数地址不显示函数名的问题
查看>>
c结构体、c++结构体和c++类的区别以及错误纠正
查看>>
Linux下查看根目录各文件内存占用情况
查看>>
A星算法详解(个人认为最详细,最通俗易懂的一个版本)
查看>>
利用栈实现DFS
查看>>
逆序对的数量(递归+归并思想)
查看>>
数的范围(二分查找上下界)
查看>>
算法导论阅读顺序
查看>>
Windows程序设计:直线绘制
查看>>