首页 文章资讯内容详情

spring-session(二)与spring-boot整合实战

2026-06-01 5 花语

本文内容纲要:

前两篇介绍了spring-session的原理,这篇在理论的基础上再实战。

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

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

源码请戮session-example

实战

搭建spring-boot工程这里飘过,传送门:https://start.spring.io/

配置spring-session

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

<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency>

编写springboot启动类SessionExampleApplication

/** *启动类 * *@authorhuaijin */ @SpringBootApplication publicclassSessionExampleApplication{ publicstaticvoidmain(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失效。

/** *登录控制器 * *@authorhuaijin */ @RestController publicclassLoginController{ privatestaticfinalStringCURRENT_USER="currentUser"; /** *登录 * *@paramloginVo登录信息 * *@authorhuaijin */ @PostMapping("/login.do") publicStringlogin(@RequestBodyLoginVologinVo,HttpServletRequestrequest){ UserVouserVo=UserVo.builder().userName(loginVo.getUserName()) .userPassword(loginVo.getUserPassword()).build(); HttpSessionsession=request.getSession(); session.setAttribute(CURRENT_USER,userVo); System.out.println("createsession,sessionIdis:"+session.getId()); return"ok"; } /** *登出 * *@authorhuaijin */ @PostMapping("/logout.do") publicStringlogout(HttpServletRequestrequest){ HttpSessionsession=request.getSession(false); session.invalidate(); return"ok"; } }

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

/** *查询 * *@authorhuaijin */ @RestController @RequestMapping("/session") publicclassQuerySessionController{ @GetMapping("/query.do") publicStringquerySessionId(HttpServletRequestrequest){ HttpSessionsession=request.getSession(false); if(session==null){ return"error"; } System.out.println("currentsuseris:"+session.getId()+"insession"); return"ok"; } } 编写Session删除事件监听器

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

/** *session事件监听器 * *@authorhuaijin */ @Component publicclassSessionEventListenerimplementsApplicationListener<SessionDeletedEvent>{ privatestaticfinalStringCURRENT_USER="currentUser"; @Override publicvoidonApplicationEvent(SessionDeletedEventevent){ Sessionsession=event.getSession(); UserVouserVo=session.getAttribute(CURRENT_USER); System.out.println("invalidsessionsuser:"+userVo.toString()); } } 验证测试

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

/** *测试Spring-Session: *1.登录时创建session *2.使用sessionId能正常访问 *3.session过期销毁,能够监听销毁事件 * *@authorhuaijin */ @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc publicclassSpringSessionTest{ @Autowired privateMockMvcmockMvc; @Test publicvoidtestLogin()throwsException{ LoginVologinVo=newLoginVo(); loginVo.setUserName("admin"); loginVo.setUserPassword("admin@123"); Stringcontent=JSON.toJSONString(loginVo); //mock登录 ResultActionsactions=this.mockMvc.perform(post("/login.do") .content(content).contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()).andExpect(content().string("ok")); StringsessionId=actions.andReturn() .getResponse().getCookie("SESSION").getValue(); //使用登录的sessionIdmock查询 this.mockMvc.perform(get("/session/query.do") .cookie(newCookie("SESSION",sessionId))) .andExpect(status().isOk()).andExpect(content().string("ok")); //mock登出 this.mockMvc.perform(post("/logout.do") .cookie(newCookie("SESSION",sessionId))) .andExpect(status().isOk()).andExpect(content().string("ok")); } }

测试类执行结果:

createsession,sessionIdis:429cb0d3-698a-475a-b3f1-09422acf2e9c currentsuseris:429cb0d3-698a-475a-b3f1-09422acf2e9cinsession invalidsessionsuser: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) classRedisSessionConfiguration{ @Configuration publicstaticclassSpringBootRedisHttpSessionConfiguration extendsRedisHttpSessionConfiguration{ //加载application.yml或者application.properties中自定义的配置项: //命名空间:用于作为sessionrediskey的一部分 //flushmode:session写入redis的模式 //定时任务时间:即访问redis过期键的定时任务的cron表达式 @Autowired publicvoidcustomize(SessionPropertiessessionProperties, RedisSessionPropertiesredisSessionProperties){ Durationtimeout=sessionProperties.getTimeout(); if(timeout!=null){ setMaxInactiveIntervalInSeconds((int)timeout.getSeconds()); } setRedisNamespace(redisSessionProperties.getNamespace()); setRedisFlushMode(redisSessionProperties.getFlushMode()); setCleanupCron(redisSessionProperties.getCleanupCron()); } } }

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

@Configuration @EnableScheduling publicclassRedisHttpSessionConfigurationextendsSpringHttpSessionConfiguration implementsBeanClassLoaderAware,EmbeddedValueResolverAware,ImportAware, SchedulingConfigurer{ //默认的cron表达式,application.yml可以自定义配置 staticfinalStringDEFAULT_CLEANUP_CRON="0*****"; //session的有效最大时间间隔,application.yml可以自定义配置 privateIntegermaxInactiveIntervalInSeconds=MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS; //session在redis中的命名空间,主要为了区分session,application.yml可以自定义配置 privateStringredisNamespace=RedisOperationsSessionRepository.DEFAULT_NAMESPACE; //session写入Redis的模式,application.yml可以自定义配置 privateRedisFlushModeredisFlushMode=RedisFlushMode.ON_SAVE; //访问过期Session集合的定时任务的定时时间,默认是每整分运行任务 privateStringcleanupCron=DEFAULT_CLEANUP_CRON; privateConfigureRedisActionconfigureRedisAction=newConfigureNotifyKeyspaceEventsAction(); //spring-data-redis的redis连接工厂 privateRedisConnectionFactoryredisConnectionFactory; //spring-data-redis的RedisSerializer,用于序列化session中存储的attributes privateRedisSerializer<Object>defaultRedisSerializer; //session时间发布者,默认注入的是AppliationContext实例 privateApplicationEventPublisherapplicationEventPublisher; //访问过期session键的定时任务的调度器 privateExecutorredisTaskExecutor; privateExecutorredisSubscriptionExecutor; privateClassLoaderclassLoader; privateStringValueResolverembeddedValueResolver; }

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

@Bean publicRedisOperationsSessionRepositorysessionRepository(){ //创建RedisOperationsSessionRepository RedisTemplate<Object,Object>redisTemplate=createRedisTemplate(); RedisOperationsSessionRepositorysessionRepository=newRedisOperationsSessionRepository( redisTemplate); //设置SessionEvent发布者。如果对此迷惑,传送门: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); returnsessionRepository; }

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

@Bean publicRedisMessageListenerContainerredisMessageListenerContainer(){ //创建MessageListener容器,这属于spring-data-redis范畴,略过 RedisMessageListenerContainercontainer=newRedisMessageListenerContainer(); 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(newPatternTopic("__keyevent@*:del"), newPatternTopic("__keyevent@*:expired"))); //模式订阅redis的${namespace}:event:created:*通道,当该向该通道发布消息, //则MessageListener消费消息并处理 container.addMessageListener(sessionRepository(), Collections.singletonList(newPatternTopic( sessionRepository().getSessionCreatedChannelPrefix()+"*"))); returncontainer; }

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

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

@Bean public<SextendsSession>SessionRepositoryFilter<?extendsSession>springSessionRepositoryFilter( SessionRepository<S>sessionRepository){ SessionRepositoryFilter<S>sessionRepositoryFilter=newSessionRepositoryFilter<>( sessionRepository); sessionRepositoryFilter.setServletContext(this.servletContext); sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver); returnsessionRepositoryFilter; }

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的设计深深体现这两大原则。 参考

SpringSession

本文内容总结:

原文链接:https://www.cnblogs.com/lxyit/p/9720159.html