美文网首页
Spring Session

Spring Session

作者: spilledyear | 来源:发表于2018-05-14 10:08 被阅读0次

    消息模式

    因为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替换掉的。

    image.png

    可以通过继承HttpServletRequestWrapper 达到修饰 HttpServletRequest 的目的。
    同理,HttpServletResponseWrapper 类似。

    相关文章

      网友评论

          本文标题:Spring Session

          本文链接:https://www.haomeiwen.com/subject/dixhdftx.html