spring boot 分布式session实现
主要是通过包装 HttpServletRequest
将 session
相关的方法进行代理。
具体是的实现就是通过 SessionRepositoryFilter
过滤器将 HttpServletRequest
对象进行包装,当调用 session
相关的方法时,代理到 SessionRepository
的实现类。
我们先看看 SessionRepository
。
public interface SessionRepository<S extends Session> { //创建session S createSession(); //保存session void save(S session); //通过session Id查找session S findById(String id); //通过session Id删除session void deleteById(String id); }
SessionRepository
是一个接口,主要用来管理 session
。各种分布式 session
处理方案都需要实现这个接口来实现具体的处理。
SessionRepositoryFilter
是一个过滤器,它的构造方法会接收一个 SessionRepository
的实现类,并且在它的filter方法中会对 HttpServletRequest
、 HttpServletResponse
进行包装,当后续调用到 session
相关的方法时,最终都会调用到 SessionRepository
方法。
SessionRepositoryFilter
继承了 OncePerRequestFilter
OncePerRequestFilter
是一个抽象类,需要子类来实现 doFilterInternal
方法来实现。这个抽象类主要用来控制每个 filter
只执行一次。在它的 doFilter
方法中,又会调用到 doFilterInternal
这个抽象方法。
这个是 SessionRepositoryFilter
的构造方法
public SessionRepositoryFilter(SessionRepository<S> sessionRepository) { if (sessionRepository == null) { throw new IllegalArgumentException("sessionRepository cannot be null"); } this.sessionRepository = sessionRepository; }
这个是 SessionRepositoryFilter
的 doFilterInternal
方法,在这个方法中可以看到分别将 request
, response
进行了包装,在这之后获取的 request
, response
实际上是 SessionRepositoryRequestWrapper
、 SessionRepositoryResponseWrapper
类型。
@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); SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest, response); try { filterChain.doFilter(wrappedRequest, wrappedResponse); } finally { wrappedRequest.commitSession(); } }
之后调用 request.getSesion()
之类 session
相关的方法实际都会调用到 SessionRepositoryRequestWrapper
的方法。
SessionRepositoryRequestWrapper
是 SessionRepositoryFilter
的内部类,所以虽然在 doFilterInternal
方法中创建 SessionRepositoryRequestWrapper
对象时,没有传递 SessionRepository
,但它依旧是可以使用的。
下面简单看下 SessionRepositoryRequestWrapper
的 getSession
方法
@Override public HttpSessionWrapper getSession() { return getSession(true); } @Override public HttpSessionWrapper getSession(boolean create) { ...... //不相关的代码已经省略,如果对应的session已经存在,就会从上面省略的地方返回对应的session。 //如果session不存在,就会在下面去创建session。 //可以看到这里是通过SessionRepositoryFilter.this.sessionRepository来创建的 S session = SessionRepositoryFilter.this.sessionRepository.createSession(); session.setLastAccessedTime(Instant.now()); currentSession = new HttpSessionWrapper(session, getServletContext()); setCurrentSession(currentSession); return currentSession; }
spring的文档也写了如何使用redis和数据库来实现分布式session。当然spring也已经实现了redis和数据库的具体实现。我们仅仅使用配置就可以来使用。
具体的文档可以查看这里https://docs.spring.io/spring-session/docs/2.2.x/reference/html/httpsession.html#httpsession-redis-jc
比如使用redis来做分布式session
我们只需要进行下面几步
1、配置redis连接的相关信息
2、通过配置启动redis session相关
上面我分别标注了1、2、3。
我们分别来看看。
-
标注1:先看下
EnableRedisHttpSession
注解
这个类会通过 Import
注解导入 RedisHttpSessionConfiguration
类。而 RedisHttpSessionConfiguration
类又是继承了 SpringHttpSessionConfiguration
。
在 RedisHttpSessionConfiguration
中会实现具体的 session
管理的相关工作。它会创建一个类型为 RedisIndexedSessionRepository
的bean。这个bean就实现了我们开头提到的 SessionRepository
接口,用来执行具体的 session
管理的相关工作。比如将 session
保存到 redis
,从 redis
查找、删除对应 session
等等具体的工作。
在 SpringHttpSessionConfiguration
中会通过注入上面创建的 RedisIndexedSessionRepository
的 bean
,创建 SessionRepositoryFilter
过滤器。
各种分布式实现方案一般都是通过这种方式来实现的。实现具体的 session
管理工作。通过 SpringHttpSessionConfiguration
来完成其他工作。
使用数据库做分布式session的时候也是继承 SpringHttpSessionConfiguration
。
-
标注2:这个是通过我们在yml中的配置来得到redis的连接工厂
-
标注3:这个主要是用来指定redis序列化的实现。
上面的是 RedisHttpSessionConfiguration
的方法,在创建 RedisTemplate
、 RedisIndexedSessionRepository
时,都会判断 defaultRedisSerializer
是否为null,不是null的情况下,会设置到 RedisTemplate
、 RedisIndexedSessionRepository
上去。默认的序列化实现,在我们在redis直接查看的时候,就会显示乱码。如下图:
注意:这里我们可以看到 RedisTemplate
并不是通过注入的方式来实现的。所以我们在外面创建 RedisTemplate
的bean对象,在这里时用不到的。
所以就需要我们通过指定序列化实现,注入到 defaultRedisSerializer
属性上。在 RedisHttpSessionConfiguration
这个类中正好有注入的方法:
所以我们就可以在我们的代码中生成 RedisSerializer
类型的 bean
,同时指定 bean
的名字为 springSessionDefaultRedisSerializer
,就可以注入上去。
现在我们在redis查看session时,就不是乱码了。
其他如使用数据库或其他方案来实现分布式session,基本都和redis是类似的。
不过由于各种数据库的语法、等等各方面会稍有差异,所以每个数据库的session的建表语句都是不同的。如文档上所说,需要指定数据库类型和建表脚本。