博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
spring-session(二)与spring-boot整合实战
阅读量:7099 次
发布时间:2019-06-28

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

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

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

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

源码请戮

实战

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

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

参考

转载于:https://www.cnblogs.com/lxyit/p/9720159.html

你可能感兴趣的文章
金融安全资讯精选 2017年第二期:金融网络安全和反欺诈方法论_金融新兴技术成熟度几何?...
查看>>
预编译指令包括:宏定义;条件编译;文件包含(就是include)
查看>>
(待编辑)贪心算法学习——会议安排问题
查看>>
getopts的使用
查看>>
lnmp安装学习
查看>>
CodeChef - QRECT Rectangle Query CDQ分治
查看>>
React Native系列(6) - 编译安卓私有React-Native代码
查看>>
初探12306售票算法(一)- 理论(转)
查看>>
shell中使用sqlplus及调试相关
查看>>
java.lang.Exception: DEBUG -- CLOSE BY CLIENT STACK TRACE 的理解
查看>>
Python学习【第23篇】:利用threading模块开线程
查看>>
C++之编码问题(Unicode,ASCII,本地默认)
查看>>
字母排序
查看>>
[日常] DNS的迭代查询过程
查看>>
[Linux] Nginx 提供静态内容和优化积压队列
查看>>
Excel VBA 基本概念
查看>>
获取文件Md5值
查看>>
Linux常用命令整理
查看>>
逛论坛时发现 有关 递归调用
查看>>
JavaScript的3大组成部分&&ECMAScript函数&&闭包
查看>>