spring session + redis实现共享session
一、代码
1.pom.xml
<!--spring session-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
2.配置application.properties
server.port=8080
spring.redis.host=localhost
spring.redis.port=6379 #spring session使用存储类型,默认就是redis所以可以省略
spring.session.store-type=redis
3.启动类
@EnableCaching
@EnableRedisHttpSession
@SpringBootApplication
public class SpringsessionApplication { public static void main(String[] args) {
SpringApplication.run(SpringsessionApplication.class, args);
} }
4.控制类
@RestController
public class IndexController { @RequestMapping("/show")
public String show(HttpServletRequest request){
return "I'm " + request.getLocalPort();
} @RequestMapping(value = "/session")
public String getSession(HttpServletRequest request) {
request.getSession().setAttribute("userName", "admin");
return "I'm " + request.getLocalPort() + " save session " + request.getSession().getId();
} @RequestMapping(value = "/get")
public String get(HttpServletRequest request) {
String userName = (String) request.getSession().getAttribute("userName");
return "I'm " + request.getLocalPort() + " userName :" + userName;
} }
启动两个端口服务,就能测试session共享成功。
(问题,1.不能解决sso单点登录
2.redis保存的数据没有序列化,导致很乱)
二、实现原理
看了上面的配置,我们知道开启 Redis Session 的“秘密”在 @EnableRedisHttpSession 这个注解上。打开 @EnableRedisHttpSession 的源码:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(RedisHttpSessionConfiguration.class)
@Configuration
public @interface EnableRedisHttpSession {
//Session默认过期时间,秒为单位,默认30分钟
int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
//配置key的namespace,默认的是spring:session,如果不同的应用共用一个redis,应该为应用配置不同的namespace,这样才能区分这个Session是来自哪个应用的
String redisNamespace() default RedisOperationsSessionRepository.DEFAULT_NAMESPACE;
//配置刷新Redis中Session的方式,默认是ON_SAVE模式,只有当Response提交后才会将Session提交到Redis
//这个模式也可以配置成IMMEDIATE模式,这样的话所有对Session的更改会立即更新到Redis
RedisFlushMode redisFlushMode() default RedisFlushMode.ON_SAVE;
//清理过期Session的定时任务默认一分钟一次。
String cleanupCron() default RedisHttpSessionConfiguration.DEFAULT_CLEANUP_CRON;
}
这个注解的作用是注册一个sessionRepositoryFilter,这个Filter会拦截所有的的请求,对session进行操作,注入sessionRepositoryFilter的代码在RedisHttpSessionConfiguration中
@Configuration
@EnableScheduling
public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration
implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware,
SchedulingConfigurer {
...
}
RedisHttpSessionConfiguration继承了springHttpSessionConfiguration,
SpringHttpSessionConfiguration中注册了SessionRepositoryFilter。
@Configuration
public class SpringHttpSessionConfiguration implements ApplicationContextAware {
...
@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;
}
...
}
我们发现注册SessionRepositoryFilter时需要一个SessionRepository参数,这个参数是在RedisHttpSessionConfiguration中被注入进去的
@Configuration
@EnableScheduling
public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration
implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware,SchedulingConfigurer { @Bean
public RedisOperationsSessionRepository sessionRepository() {
RedisTemplate<Object, Object> redisTemplate = createRedisTemplate();
RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(redisTemplate);
sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
if (this.defaultRedisSerializer != null) {
sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
}
sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
if (StringUtils.hasText(this.redisNamespace)) {
sessionRepository.setRedisKeyNamespace(this.redisNamespace);
}
sessionRepository.setRedisFlushMode(this.redisFlushMode);
int database = resolveDatabase();
sessionRepository.setDatabase(database);
return sessionRepository;
}
}
SessionRepositoryFilter 拦截到请求后,会先将 request 和 response 对象转换成 Spring 内部的包装类 SessionRepositoryRequestWrapper 和 SessionRepositoryResponseWrapper 对象。SessionRepositoryRequestWrapper 类重写了原生的getSession
方法。代码如下:
@Override
public HttpSessionWrapper getSession(boolean create) {
//通过request的getAttribue方法查找CURRENT_SESSION属性,有直接返回
HttpSessionWrapper currentSession = getCurrentSession();
if (currentSession != null) {
return currentSession;
}
//查找客户端中一个叫SESSION的cookie,通过sessionRepository对象根据SESSIONID去Redis中查找Session
S requestedSession = getRequestedSession();
if (requestedSession != null) {
if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
requestedSession.setLastAccessedTime(Instant.now());
this.requestedSessionIdValid = true;
currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
currentSession.setNew(false);
//将Session设置到request属性中
setCurrentSession(currentSession);
//返回Session
return currentSession;
}
}
else {
// This is an invalid session id. No need to ask again if
// request.getSession is invoked for the duration of this request
if (SESSION_LOGGER.isDebugEnabled()) {
SESSION_LOGGER.debug(
"No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
}
setAttribute(INVALID_SESSION_ID_ATTR, "true");
}
//不创建Session就直接返回null
if (!create) {
return null;
}
if (SESSION_LOGGER.isDebugEnabled()) {
SESSION_LOGGER.debug(
"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
+ SESSION_LOGGER_NAME,
new RuntimeException(
"For debugging purposes only (not an error)"));
}
//通过sessionRepository创建RedisSession这个对象,可以看下这个类的源代码,如果
//@EnableRedisHttpSession这个注解中的redisFlushMode模式配置为IMMEDIATE模式,会立即
//将创建的RedisSession同步到Redis中去。默认是不会立即同步的。
S session = SessionRepositoryFilter.this.sessionRepository.createSession();
session.setLastAccessedTime(Instant.now());
currentSession = new HttpSessionWrapper(session, getServletContext());
setCurrentSession(currentSession);
return currentSession;
}
当调用 SessionRepositoryRequestWrapper 对象的getSession
方法拿 Session 的时候,会先从当前请求的属性中查找CURRENT_SESSION
属性,如果能拿到直接返回,这样操作能减少Redis操作,提升性能。
到现在为止我们发现如果redisFlushMode
配置为 ON_SAVE 模式的话,Session 信息还没被保存到 Redis 中,那么这个同步操作到底是在哪里执行的呢?
仔细看代码,我们发现 SessionRepositoryFilter 的doFilterInternal
方法最后有一个 finally 代码块,这个代码块的功能就是将 Session同步到 Redis。
@Override
protected void doFilterInternal(HttpServletRequest request,HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
request, response, this.servletContext);
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
wrappedRequest, response);
try {
filterChain.doFilter(wrappedRequest, wrappedResponse);
}
finally {
//将Session同步到Redis,同时这个方法还会将当前的SESSIONID写到cookie中去,同时还会发布一
//SESSION创建事件到队列里面去
wrappedRequest.commitSession();
}
}
主要的核心类有:
- @EnableRedisHttpSession:开启 Session 共享功能;
- RedisHttpSessionConfiguration:配置类,一般不需要我们自己配置,主要功能是配置 SessionRepositoryFilter 和 RedisOperationsSessionRepository 这两个Bean;
- SessionRepositoryFilter:拦截器,Spring-Session 框架的核心;
- RedisOperationsSessionRepository:可以认为是一个 Redis 操作的客户端,有在 Redis 中进行增删改查 Session 的功能;
- SessionRepositoryRequestWrapper:Request 的包装类,主要是重写了
getSession
方法 - SessionRepositoryResponseWrapper:Response的包装类。
原理简要总结:
当请求进来的时候,SessionRepositoryFilter 会先拦截到请求,将 request 和 response 对象转换成 SessionRepositoryRequestWrapper 和 SessionRepositoryResponseWrapper 。后续当第一次调用 request 的getSession方法时,会调用到 SessionRepositoryRequestWrapper 的getSession
方法。这个方法是被从写过的,逻辑是先从 request 的属性中查找,如果找不到;再查找一个key值是"SESSION"的 Cookie,通过这个 Cookie 拿到 SessionId 去 Redis 中查找,如果查不到,就直接创建一个RedisSession 对象,同步到 Redis 中。
spring session + redis实现共享session的更多相关文章
- 基于Spring Boot/Spring Session/Redis的分布式Session共享解决方案
分布式Web网站一般都会碰到集群session共享问题,之前也做过一些Spring3的项目,当时解决这个问题做过两种方案,一是利用nginx,session交给nginx控制,但是这个需要额外工作较多 ...
- Spring Session + Redis实现分布式Session共享
发表于 2016-09-29 文章目录 1. Maven依赖 2. 配置Filter 3. Spring配置文件 4. 解决Redis云服务Unable to configure Redis to k ...
- SpringBoot进阶教程(二十六)整合Redis之共享Session
集群现在越来越常见,当我们项目搭建了集群,就会产生session共享问题.因为session是保存在服务器上面的.那么解决这一问题,大致有三个方案,1.通过nginx的负载均衡其中一种ip绑定来实现( ...
- 修改记录-优化后(springboot+shiro+session+redis+ngnix共享)
1.普通用户实现redis共享session 1.配置 #cache指定缓存类型 spring.cache.type=REDIS #data-redis spring.redis.database=1 ...
- 负载均衡中使用 Redis 实现共享 Session
最近在研究Web架构方面的知识,包括数据库读写分离,Redis缓存和队列,集群,以及负载均衡(LVS),今天就来先学习下我在负载均衡中遇到的问题,那就是session共享的问题. 一.负载均衡 负载均 ...
- Spring Boot 应用使用spring session+redis启用分布式session后,如何在配置文件里设置应用的cookiename、session超时时间、redis存储的namespace
现状 项目在使用Spring Cloud搭建微服务框架,其中分布式session采用spring session+redis 模式 需求 希望可以在配置文件(application.yml)里设置应用 ...
- linux php 中session 多站点共享session问题
linux php 中session默认file 假如修改为redis php.ini session.save_handler = "files"; session.save_p ...
- springboot+spring session+redis+nginx实现session共享和负载均衡
环境 centos7. jdk1.8.nginx.redis.springboot 1.5.8.RELEASE session共享 添加spring session和redis依赖 <depen ...
- tomcat使用redis存储共享session
在tomcat集群环境下实现session共享有几种解决方式,这里介绍一种简单的方案. 使用redis对session进行存储,配置比較简单.webserver是tomcat6 1.下载jar包: c ...
- redis+tomcat共享session问题(转)
为了让楼主看看Java开发是多么的简单和轻松,你说你耗时一周都没搞好,简直就是胡说八道!!我从没搞过reids和tomcat整合的(我做的项目一直都是去session用cookie,为了验证你在胡说八 ...
随机推荐
- kafka配置内外网同时访问
#内网监听名称,这个在配置文件中没有需要添加 inter.broker.listener.name=INTERNAL #内网监听规则,第一个是内网,第二个是外网,注意端口不一样,端口可以自己定义 li ...
- spring@Validated校验用法
1.controller添加注解 public BaseResponse addOrUpdateUnit(@RequestBody @Validated RiskUnitDto riskUnitDto ...
- 002. html篇之《表格》
html篇之<表格> 1. 结构 <table> <!-- 表格标签 --> <caption>标题,自动居中对齐</caption> &l ...
- gopher必读文章
Getting Started with Go Programminghttps://www.programiz.com/golang/getting-startedHow to Write Go C ...
- npm vue-router安装报错
因为2022年2月7日以后,vue-router的默认版本,为4版本,而且 vue-router4,只能在vue3中,只有vue-router3中,能用在vue 2中 如果把vue-router4强制 ...
- macOS NSView改变frame后会出现黑色残留,应付的办法是不在drawRect上修改重新initWithFrame一下就行
黑色部分就是残留.是因为绘制后保留了轨迹. 解决办法是不在drawRect中做处理重新写NSView,新增方法 initWithFrame - (void)drawRect:(NSRect)dirty ...
- Fortran笔记之过程重载,多态,泛型编程
参考自Introduction to Modern Fortran for the Earth System Sciences 过程重载 OOP中的另一个重要技术是过程重载(Procedure Ove ...
- Verilog仿真遇到的问题
1.Vivado 15.4仿真时编译没有报错,但是仿真不成功,逻辑很简单,full为高电平时,rd_en要拉高,但全程没有拉高! 检查语法发现语句" else if( empty == 'b ...
- Python 面试题整理
一.语言特性 1.什么是Python?使用Python有什么好处?Python和其他语言的区别? Python是一种编程语言,它有对象,模块,线程,异常处理和自动内存管理. 好处:开源.简洁.简单.方 ...
- 综合java admin后台记录
在新冠将来未来的气氛下,做一些年终封箱吧,这个事没做完,但暂时可能也没时间做,待来年了 https://hooray.gitee.io/fantastic-admin/ https://github. ...