<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【源碼在文末】SpringSession實(shí)戰(zhàn)使用(基于SpringBoot項(xiàng)目)

          共 9885字,需瀏覽 20分鐘

           ·

          2020-11-21 22:38

          者:懷瑾握瑜

          來(lái)源鏈接:

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

          spring-boot 整合 spring-session 的自動(dòng)配置可謂是開(kāi)箱即用,極其簡(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 中依賴:


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


          編寫(xiě) 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_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

          編寫(xiě) controller

          編寫(xiě)登錄控制器,登錄時(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";
          ????}
          }

          編寫(xiě)查詢控制器,在登錄創(chuàng)建 session 后,使用將 sessionId 置于 cookie 中訪問(wèn)。如果沒(méi)有 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";
          ????}
          }

          編寫(xiě) Session 刪除事件監(jiān)聽(tīng)器

          Session 刪除事件監(jiān)聽(tīng)器用于監(jiān)聽(tīng)登出時(shí)使 session 失效的事件源。

          /**
          ?*?session事件監(jiān)聽(tīng)器
          ?*
          ?*?@author?huaijin
          ?*/
          @Component
          public?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());
          ????}
          }

          驗(yàn)證測(cè)試

          編寫(xiě) spring-boot 測(cè)試類,測(cè)試 controller,驗(yàn)證 spring-session 是否生效。


          /**
          ?*?測(cè)試Spring-Session:
          ?*?1.登錄時(shí)創(chuàng)建session
          ?*?2.使用sessionId能正常訪問(wèn)
          ?*?3.session過(guò)期銷毀,能夠監(jiān)聽(tīng)銷毀事件
          ?*
          ?*?@author?huaijin
          ?*/
          @RunWith(SpringRunner.class)
          @SpringBootTest
          @AutoConfigureMockMvc
          public?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"));
          ????}
          }

          測(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 過(guò)期。

          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?{

          ?@Configuration
          ?public?static?class?SpringBootRedisHttpSessionConfiguration
          ???extends?RedisHttpSessionConfiguration?{

          ??//?加載application.yml或者application.properties中自定義的配置項(xiàng):
          ??//?命名空間:用于作為session redis key的一部分
          ??// flushmode:session寫(xiě)入redis的模式
          ??//?定時(shí)任務(wù)時(shí)間:即訪問(wèn)redis過(guò)期鍵的定時(shí)任務(wù)的cron表達(dá)式
          ??@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
          @EnableScheduling
          public?class?RedisHttpSessionConfiguration?extends?SpringHttpSessionConfiguration
          ??implements?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寫(xiě)入Redis的模式,application.yml可以自定義配置
          ?private?RedisFlushMode?redisFlushMode?=?RedisFlushMode.ON_SAVE;

          ?//?訪問(wèn)過(guò)期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ǔ)的attributes
          ?private?RedisSerializer?defaultRedisSerializer;

          ?//?session時(shí)間發(fā)布者,默認(rèn)注入的是AppliationContext實(shí)例
          ?private?ApplicationEventPublisher?applicationEventPublisher;

          ?//?訪問(wèn)過(guò)期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)建RedisOperationsSessionRepository
          ?RedisTemplate?redisTemplate?=?createRedisTemplate();
          ?RedisOperationsSessionRepository?sessionRepository?=?new?RedisOperationsSessionRepository(
          ???redisTemplate);
          ?//?設(shè)置Session Event發(fā)布者。如果對(duì)此迷惑,傳送門:https://www.cnblogs.com/lxyit/p/9719542.html
          ?sessionRepository.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è)置寫(xiě)redis的模式
          ?sessionRepository.setRedisFlushMode(this.redisFlushMode);
          ?return?sessionRepository;
          }

          同時(shí)也初始化了 Session 事件監(jiān)聽(tīng)器 MessageListener 模塊

          @Bean
          public?RedisMessageListenerContainer?redisMessageListenerContainer()?{
          ?//?創(chuàng)建MessageListener容器,這屬于spring-data-redis范疇,略過(guò)
          ?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的鍵過(guò)期和刪除事件通知
          ?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)聽(tīng) Redis 的 channel,使用 Redis 的鍵空間通知處理 Session 的刪除和過(guò)期事件和使用 Pub/Sub 模式處理 Session 創(chuàng)建事件。

          關(guān)于 RedisSession 的存儲(chǔ)管理部分已經(jīng)初始化,但是 spring-session 的另一個(gè)基礎(chǔ)設(shè)施模塊 SessionRepositoryFilter 是在 RedisHttpSessionConfiguration 父類 SpringHttpSessionConfiguration 中初始化。

          @Bean
          public??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 自動(dòng)配置 spring-session,非常合理。

          • SpringHttpSessionConfiguration 是 spring-session 本身的配置類,與 spring-boot 無(wú)關(guān),畢竟 spring-session 也可以整合單純的 spring 項(xiàng)目,只需要使用該 spring-session 的配置類即可。
          • RedisHttpSessionConfiguration 用于配置 spring-session 的 Redission,畢竟 spring-session 還支持其他的各種 session:Map/JDBC/MogonDB 等,將其從 SpringHttpSessionConfiguration 隔離開(kāi)來(lái),遵循開(kāi)閉原則和接口隔離原則。但是其必須依賴基礎(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ì)模式。遵循開(kāi)閉原則:對(duì)修改關(guān)閉,對(duì)擴(kuò)展開(kāi)放。遵循接口隔離原則:變化的就要單獨(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

          更多精彩推薦

          ??外包程序員入職螞蟻金服被質(zhì)疑,網(wǎng)友:人生污點(diǎn)
          ??前后端分離三連問(wèn):為何分離?如何分離?分離后的接口規(guī)范?
          ??如何設(shè)計(jì)一個(gè)通用的權(quán)限管理系統(tǒng)
          ??去一家小公司從0到1搭建后端架構(gòu),做個(gè)總結(jié)!
          ??這應(yīng)該是全網(wǎng)最全的Git分支開(kāi)發(fā)規(guī)范手冊(cè)~

          最后,推薦給大家一個(gè)有趣有料的公眾號(hào):寫(xiě)代碼的渣渣鵬,7年老程序員教你寫(xiě)bug,回復(fù) 面試或資源 送一你整套開(kāi)發(fā)筆記 有驚喜哦

          瀏覽 60
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    青草91 | 国产精品久久777777换脸 | 俺来也成人免费视频 | 国产女人水真多18精品 | 日本三级天天在干 |