这篇博客我们简单介绍一下对Java web 实现Session共享的几种实现方式,及通过分析Spring Session来看看Spring 对Session共享是如何实现的。
一、Session 共享实现方式
1、通过tomcat的实现机制来实现,简单来说在tomcat容器中它完成了对Session的创建和管理等功能,如果能修改这部分代码就可以实现基于tomcat容器的session共享,算是在服务端实现了,tomcat Session管理的博客可以看《Tomcat源码学习--Session创建销毁》,tomcat实现Session共享在github中有开源项目tomcat-redis-session-manager 简单来说tomcat已经提供了扩展机制,让开发人员可以改造其Session管理的模块
2、自己实现HttpSession的实现机制,所有要保存到HttpSession中的数据都自己进行处理,之前所在公司有个项目就是如此实现的。
3、Spring Session提供的Session共享机制,其不再容器服务中进行改造,实在web应用中进行处理的,和第二种实现方式有点类似。
二、Spring Session 共享实现原理
1、引入 Spring Session相关jar
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>1.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
2、注入Spring Session运行需要的Bean
<bean id="redisHttpSessionConfiguration"
class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="600"/>
</bean>
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="100" />
<property name="maxIdle" value="10" />
</bean>
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy">
<property name="hostName" value="${redis_hostname}"/>
<property name="port" value="${redis_port}"/>
<property name="password" value="${redis_pwd}" />
<property name="timeout" value="3000"/>
<property name="usePool" value="true"/>
<property name="poolConfig" ref="jedisPoolConfig"/>
</bean>
3、在web.xml中配置拦截器,为什么要将此Filter在web.xml中配置为第一个Filter的,因为由于Session共享的机制,你不确定其他Filter中是否有使用Session相关的操作,如果存在可能会出现问题。
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
4、实现原理
(1)在注入RedisHttpSessionConfiguration时会将SessionRepositoryFilter注入,这个就是Spring Session实现session共享的Filter
@Bean
public <S extends ExpiringSession> SessionRepositoryFilter<? extends ExpiringSession> springSessionRepositoryFilter(
SessionRepository<S> sessionRepository) {
SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<S>(
sessionRepository);
sessionRepositoryFilter.setServletContext(this.servletContext);
if (this.httpSessionStrategy instanceof MultiHttpSessionStrategy) {
sessionRepositoryFilter.setHttpSessionStrategy(
(MultiHttpSessionStrategy) this.httpSessionStrategy);
}
else {
sessionRepositoryFilter.setHttpSessionStrategy(this.httpSessionStrategy);
}
return sessionRepositoryFilter;
}
(2)接下来我们看看SessionRepositoryFilter中做了什么处理操作来实现Session共享的,接下来我们看看其doInternalFilter方法,在这个方法中我们看到request和response都做了包装处理,那样我们看看包装类SessionRepositoryRequestWrapper中有关Session获取的方法中做了什么处理。
@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);
HttpServletRequest strategyRequest = this.httpSessionStrategy
.wrapRequest(wrappedRequest, wrappedResponse);
HttpServletResponse strategyResponse = this.httpSessionStrategy
.wrapResponse(wrappedRequest, wrappedResponse);
try {
filterChain.doFilter(strategyRequest, strategyResponse);
}
finally {
wrappedRequest.commitSession();
}
}
(3)在SessionRepositoryRequestWrapper中根据sessionId获取session方法中看到了从sessionRepository中根据sessionId获取Session,sessionRepository存在实现类RedisOperationsSessionRepository实现了将Session保存到redis的操作(当然还有其他实现类)
private S getSession(String sessionId) {
S session = SessionRepositoryFilter.this.sessionRepository
.getSession(sessionId);
if (session == null) {
return null;
}
session.setLastAccessedTime(System.currentTimeMillis());
return session;
}
总结:Spring Session通过创建一个Filter,然后在Filter中对request和response进行包装,在包装的request中重写session保存和获取的操作,完成了将session保存到redis的操作,实现了session共享。