javascript
【源码在文末】SpringSession实战使用(基于SpringBoot项目)
spring-boot 整合 spring-session 的自動(dòng)配置可謂是開箱即用,極其簡(jiǎn)潔和方便。這篇文章即介紹 spring-boot 整合 spring-session,這里只介紹基于 RedisSession 的實(shí)戰(zhàn)。
考慮到 RedisSession 模塊與 spring-session v2.0.6 版本的差異很小,且能夠與 spring-boot v2.0.0 兼容,所以實(shí)戰(zhàn)篇是基于 spring-boot v2.0.0 基礎(chǔ)上配置 spring-session。
配置 spring-session
引入 spring-session 的 pom 配置,由于 spring-boot 包含 spring-session 的 starter 模塊,所以 pom 中依賴:
<dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId> </dependency>編寫 spring boot 啟動(dòng)類 SessionExampleApplication
/***?啟動(dòng)類**?@author?huaijin*/ @SpringBootApplication public?class?SessionExampleApplication?{public?static?void?main(String[]?args)?{SpringApplication.run(SessionExampleApplication.class,?args);} }配置 application.yml
spring:session:redis:flush-mode:?on_savenamespace:?session.examplecleanup-cron:?0?*?*?*?*?*store-type:?redistimeout:?1800redis:host:?localhostport:?6379jedis:pool:max-active:?100max-wait:?10max-idle:?10min-idle:?10database:?0編寫 controller
編寫登錄控制器,登錄時(shí)創(chuàng)建 session,并將當(dāng)前登錄用戶存儲(chǔ) sesion 中。登出時(shí),使 session 失效。
/***?登錄控制器**?@author?huaijin*/ @RestController public?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";} }編寫查詢控制器,在登錄創(chuàng)建 session 后,使用將 sessionId 置于 cookie 中訪問。如果沒有 session 將返回錯(cuò)誤。
/***?查詢**?@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 刪除事件監(jiān)聽器
Session 刪除事件監(jiān)聽器用于監(jiān)聽登出時(shí)使 session 失效的事件源。
/***?session事件監(jiān)聽器**?@author?huaijin*/ @Component public?class?SessionEventListener?implements?ApplicationListener<SessionDeletedEvent>?{private?static?final?String?CURRENT_USER?=?"currentUser";@Overridepublic?void?onApplicationEvent(SessionDeletedEvent?event)?{Session?session?=?event.getSession();UserVo?userVo?=?session.getAttribute(CURRENT_USER);System.out.println("invalid?session's?user:"?+?userVo.toString());} }驗(yàn)證測(cè)試
編寫 spring-boot 測(cè)試類,測(cè)試 controller,驗(yàn)證 spring-session 是否生效。
/***?測(cè)試Spring-Session:*?1.登錄時(shí)創(chuàng)建session*?2.使用sessionId能正常訪問*?3.session過期銷毀,能夠監(jiān)聽銷毀事件**?@author?huaijin*/ @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public?class?SpringSessionTest?{@Autowiredprivate?MockMvc?mockMvc;@Testpublic?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"));} }測(cè)試類執(zhí)行結(jié)果:
create?session,?sessionId?is:429cb0d3-698a-475a-b3f1-09422acf2e9c current's?user?is:429cb0d3-698a-475a-b3f1-09422acf2e9cin?session invalid?session's?user:UserVo{userName='admin',?userPassword='admin@123'登錄時(shí)創(chuàng)建 Session,存儲(chǔ)當(dāng)前登錄用戶。然后在以登錄響應(yīng)返回的 SessionId 查詢用戶。最后再登出使 Session 過期。
spring-boot 整合 spring-session 自動(dòng)配置原理
前兩篇文章介紹 spring-session 原理時(shí),總結(jié) spring-session 的核心模塊。這節(jié)中探索 spring-boot 中自動(dòng)配置如何初始化 spring-session 的各個(gè)核心模塊。
spring-boot-autoconfigure 模塊中包含了 spinrg-session 的自動(dòng)配置。包 org.springframework.boot.autoconfigure.session 中包含了 spring-session 的所有自動(dòng)配置項(xiàng)。
其中 RedisSession 的核心配置項(xiàng)是 RedisHttpSessionConfiguration 類。
@Configuration @ConditionalOnClass({?RedisTemplate.class,?RedisOperationsSessionRepository.class?}) @ConditionalOnMissingBean(SessionRepository.class) @ConditionalOnBean(RedisConnectionFactory.class) @Conditional(ServletSessionCondition.class) @EnableConfigurationProperties(RedisSessionProperties.class) class?RedisSessionConfiguration?{@Configurationpublic?static?class?SpringBootRedisHttpSessionConfigurationextends?RedisHttpSessionConfiguration?{//?加載application.yml或者application.properties中自定義的配置項(xiàng)://?命名空間:用于作為session redis key的一部分// flushmode:session寫入redis的模式//?定時(shí)任務(wù)時(shí)間:即訪問redis過期鍵的定時(shí)任務(wù)的cron表達(dá)式@Autowiredpublic?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 @EnableScheduling public?class?RedisHttpSessionConfiguration?extends?SpringHttpSessionConfigurationimplements?BeanClassLoaderAware,?EmbeddedValueResolverAware,?ImportAware,SchedulingConfigurer?{//?默認(rèn)的cron表達(dá)式,application.yml可以自定義配置static?final?String?DEFAULT_CLEANUP_CRON?=?"0?*?*?*?*?*";//?session的有效最大時(shí)間間隔,?application.yml可以自定義配置private?Integer?maxInactiveIntervalInSeconds?=?MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;//?session在redis中的命名空間,主要為了區(qū)分session,application.yml可以自定義配置private?String?redisNamespace?=?RedisOperationsSessionRepository.DEFAULT_NAMESPACE;//?session寫入Redis的模式,application.yml可以自定義配置private?RedisFlushMode?redisFlushMode?=?RedisFlushMode.ON_SAVE;//?訪問過期Session集合的定時(shí)任務(wù)的定時(shí)時(shí)間,默認(rèn)是每整分運(yùn)行任務(wù)private?String?cleanupCron?=?DEFAULT_CLEANUP_CRON;private?ConfigureRedisAction?configureRedisAction?=?new?ConfigureNotifyKeyspaceEventsAction();//?spring-data-redis的redis連接工廠private?RedisConnectionFactory?redisConnectionFactory;//?spring-data-redis的RedisSerializer,用于序列化session中存儲(chǔ)的attributesprivate?RedisSerializer<Object>?defaultRedisSerializer;//?session時(shí)間發(fā)布者,默認(rèn)注入的是AppliationContext實(shí)例private?ApplicationEventPublisher?applicationEventPublisher;//?訪問過期session鍵的定時(shí)任務(wù)的調(diào)度器private?Executor?redisTaskExecutor;private?Executor?redisSubscriptionExecutor;private?ClassLoader?classLoader;private?StringValueResolver?embeddedValueResolver; }該配置類中初始化了 RedisSession 的最為核心模塊之一 RedisOperationsSessionRepository。
@Bean public?RedisOperationsSessionRepository?sessionRepository()?{//?創(chuàng)建RedisOperationsSessionRepositoryRedisTemplate<Object,?Object>?redisTemplate?=?createRedisTemplate();RedisOperationsSessionRepository?sessionRepository?=?new?RedisOperationsSessionRepository(redisTemplate);//?設(shè)置Session Event發(fā)布者。如果對(duì)此迷惑,傳送門:https://www.cnblogs.com/lxyit/p/9719542.htmlsessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);if?(this.defaultRedisSerializer?!=?null)?{sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);}//?設(shè)置默認(rèn)的Session最大有效期間隔sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);//?設(shè)置命名空間if?(StringUtils.hasText(this.redisNamespace))?{sessionRepository.setRedisKeyNamespace(this.redisNamespace);}//?設(shè)置寫redis的模式sessionRepository.setRedisFlushMode(this.redisFlushMode);return?sessionRepository; }同時(shí)也初始化了 Session 事件監(jiān)聽器 MessageListener 模塊
@Bean public?RedisMessageListenerContainer?redisMessageListenerContainer()?{//?創(chuàng)建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:*通道,當(dāng)該向該通道發(fā)布消息,//?則MessageListener消費(fèi)消息并處理container.addMessageListener(sessionRepository(),Collections.singletonList(new?PatternTopic(sessionRepository().getSessionCreatedChannelPrefix()?+?"*")));return?container; }上篇文章中介紹到的 spring-session event 事件原理,spring-session 在啟動(dòng)時(shí)監(jiān)聽 Redis 的 channel,使用 Redis 的鍵空間通知處理 Session 的刪除和過期事件和使用 Pub/Sub 模式處理 Session 創(chuàng)建事件。
關(guān)于 RedisSession 的存儲(chǔ)管理部分已經(jīng)初始化,但是 spring-session 的另一個(gè)基礎(chǔ)設(shè)施模塊 SessionRepositoryFilter 是在 RedisHttpSessionConfiguration 父類 SpringHttpSessionConfiguration 中初始化。
@Bean public?<S?extends?Session>?SessionRepositoryFilter<??extends?Session>?springSessionRepositoryFilter(SessionRepository<S>?sessionRepository)?{SessionRepositoryFilter<S>?sessionRepositoryFilter?=?new?SessionRepositoryFilter<>(sessionRepository);sessionRepositoryFilter.setServletContext(this.servletContext);sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver);return?sessionRepositoryFilter; }spring-boot 整合 spring-session 配置的層次:
RedisSessionConfiguration|_?_?SpringBootRedisHttpSessionConfiguration|_?_?RedisHttpSessionConfiguration|_?_?SpringHttpSessionConfiguration回顧思考 spring-boot 自動(dòng)配置 spring-session,非常合理。
SpringHttpSessionConfiguration 是 spring-session 本身的配置類,與 spring-boot 無關(guān),畢竟 spring-session 也可以整合單純的 spring 項(xiàng)目,只需要使用該 spring-session 的配置類即可。
RedisHttpSessionConfiguration 用于配置 spring-session 的 Redission,畢竟 spring-session 還支持其他的各種 session:Map/JDBC/MogonDB 等,將其從 SpringHttpSessionConfiguration 隔離開來,遵循開閉原則和接口隔離原則。但是其必須依賴基礎(chǔ)的 SpringHttpSessionConfiguration,所以使用了繼承。RedisHttpSessionConfiguration 是 spring-session 和 spring-data-redis 整合配置,需要依賴 spring-data-redis。
SpringBootRedisHttpSessionConfiguration 才是 spring-boot 中關(guān)鍵配置
RedisSessionConfiguration 主要用于處理自定義配置,將 application.yml 或者 application.properties 的配置載入。
Tips:
配置類也有相當(dāng)強(qiáng)的設(shè)計(jì)模式。遵循開閉原則:對(duì)修改關(guān)閉,對(duì)擴(kuò)展開放。遵循接口隔離原則:變化的就要單獨(dú)分離,使用不同的接口隔離。SpringHttpSessionConfiguration 和 RedisHttpSessionConfiguration 的設(shè)計(jì)深深體現(xiàn)這兩大原則。
參考
Spring Session參考文:https://spring.io/projects/spring-session#samples
本文例子源碼
https://github.com/lixyou/spring-boot-example/tree/master/session-example
作者:懷瑾握瑜
來源鏈接:
https://www.cnblogs.com/lxyit/p/9720159.html
總結(jié)
以上是生活随笔為你收集整理的【源码在文末】SpringSession实战使用(基于SpringBoot项目)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 工信部发布《移动互联网应用程序个人信息保
- 下一篇: 抽象工厂模式