消息模式
因为http是本身是一个无状态协议。为了记录用户的状态,session机制就应运而生了,servlet的标准本身是包含session的,Tomcat会把session的信息存储在服务器内存里,Request提供了获取session的方法。然而这种session数据没有一定的持久化机制,而且难以实现应用服务器的水平扩展。在负载均衡器 + 应用服务器集群的架构中,session共享是一个基本的要求。Spring Session支持把session信息以各种形式存储,Spring Session支持把session信息以各种形式存储,比如数据库或者Redis。项目上一般都是将Session存放在Redis中,session数据都是有时效性的,Redis的数据超时机制可以很好的完成session信息的清除;此外,Redis的数据访问速度更快,对于时效性较强的session数据,会有比较好的加速效果。
spring redis
使用
在web.xml文件中添加一下过滤器。
<filter>
<filter-name>spring-session</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>springSession</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>spring-session</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意这里的
<init-param>
<param-name>targetBeanName</param-name>
<param-value>springSession</param-value>
</init-param>
这里配置的这个targetBeanName在下面的配置文件中是有用到的!如果未指定init-param参数的话,DelegatingFilterProxy就会把filter-name作为要查找的Bean对象,这也是DelegatingFilterProxy类的作用。如果配置了,就会去找 ID 为 "springSession" 的Bean 作为 Spring-Session的 过滤器。 可以看出每一个请求都会经过该filter,经过该filter的请求也会相应的经过springSessionRepositoryFilter这个过滤器。
redis.useSentinel=false
redis.ip=localhost
redis.port=6379
redis.db=2
redis.password=
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
<!-- 引入config.properties属性文件 -->
<context:property-placeholder location="classpath:config.properties"/>
<bean id="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper">
</bean>
<bean id="redisSentinelConfiguration"
class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
<property name="master">
<bean class="org.springframework.data.redis.connection.RedisNode">
<property name="name" value="mymaster"/>
</bean>
</property>
<property name="sentinels" ref="redisNodes"/>
</bean>
<bean id="redisNodes" class="com.hand.hap.core.impl.RedisNodeAutoConfig">
<property name="sentinels" value="${redis.sentinel}"/>
</bean>
<!--这里添加的是Redis,因为使用的是Spring里自带的Redis的Session策略 -->
<bean id="v2redisConnectionFactory" class="com.hand.hap.core.JedisConnectionFactoryBean">
<property name="useSentinel" value="${redis.useSentinel}"/>
<property name="sentinelConfiguration" ref="redisSentinelConfiguration"/>
<property name="hostName" value="${redis.ip:localhost}"/>
<property name="port" value="${redis.port:6379}"/>
<property name="database" value="${redis.db:10}"/>
<property name="password" value="${redis.password:}"/>
<property name="poolConfig">
<bean class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="999"/>
<property name="maxTotal" value="9999"/>
<property name="minIdle" value="20"/>
</bean>
</property>
</bean>
<bean id="stringRedisSerializer"
class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
<bean id="v2redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="v2redisConnectionFactory"/>
<property name="keySerializer" ref="stringRedisSerializer"/>
<property name="valueSerializer" ref="stringRedisSerializer"/>
<property name="hashKeySerializer" ref="stringRedisSerializer"/>
<property name="hashValueSerializer" ref="stringRedisSerializer"/>
</bean>
<!-- 这里的是为了下面的 Session策略过滤器提供构造函数传入的参数,因为Session过滤器要依赖该对象来构造,所以创建一个先 -->
<bean name="redisOperationsSessionRepository"
class="org.springframework.session.data.redis.RedisOperationsSessionRepository">
<constructor-arg ref="v2redisConnectionFactory"/>
<property name="defaultMaxInactiveInterval" value="${session.expire.time:3600}"/>
</bean>
<!-- 这个是Session策略过滤器,即将容器原有的Session持久化机制,代替为Spring的 Redis持久化Session机制。 -->
<!-- 注意,这个名字与 web.xml里的targetBean的下value是要一致的。 -->
<bean name="springSession" class="org.springframework.session.web.http.SessionRepositoryFilter">
<constructor-arg ref="redisOperationsSessionRepository"/>
<property name="httpSessionStrategy" ref="cookieHttpSessionStrategy"/>
</bean>
</beans>
大概就是 SessionRepositoryFilter 拦截所有请求,springSessionRepositoryFilter替换容器(Tomcat)默认的HttpSession支持为Spring Session,将Session实例存放在Redis中。可以看看它的关键代码
@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends ExpiringSession> extends OncePerRequestFilter {
@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();
}
}
......
}
- SessionRepositoryRequestWrapper是HttpServletRequest的子类,接管原来的request,重写了与session相关的方法。
- SessionRepositoryResponseWrapper是HttpServletResponse的子类,接管原来的response。
因为SessionRepositoryRequestWrapper继承了HttpServletRequestWrapper,而HttpServletRequestWrapper实现了HttpServletRequest接口,在SessionRepositoryRequestWrapper又重写了HttpServletRequest接口中的一些方法,所以才会有:getSession、changeSessionId等这些方法。
到此,我们应该大致明白了,原有的request请求和response都被重新进行了包装。我们也就明白了原有的HttpSeesion是如何被Spring Session替换掉的。
可以通过继承HttpServletRequestWrapper 达到修饰 HttpServletRequest 的目的。
同理,HttpServletResponseWrapper 类似。