spring session 简单的了解

spring session 简单的了解

主要参考链接:
通过Spring Session实现新一代的Session管理
如何区分不同的用户
spring-session简介、使用及实现原理
sessionid如何产生?由谁产生?保存在哪里?
学习Spring-Session+Redis实现session共享
利用nginx实现负载均衡
主要是看了腾讯课堂上的动脑学院的2017-12-15解决session一致性问题后对于spring-session的简单的了解,对于一些基本的概念有些模糊,慢慢的去了解,必然会有所收益。

原来以为对于session和cookie的实现机制还是比较的了解的,还是发现有些不足

  • 浏览器是如何区分不同的用户?虽然我们一直认为session是保存在服务端、cookie保存在客户端的,但是我们登录后的用户是如何区分当前的用户的id额,说道这里我发现了自己以前没有好好的思考过这个问题,参考链接中如何区分不同的用户和session id如何产生的都有详细的介绍,具体就是呢(虽然Session保存在服务器,对客户端是透明的,它的正常运行仍然需要客户端浏览器的支持。这是因为Session需要使用Cookie作为识别标志。HTTP协议是无状态的,Session不能依据HTTP连接来判断是否为同一客户,因此服务器向客户端浏览器发送一个名为JSESSIONID的Cookie,它的值为该Session的id(也就是HttpSession.getId()的返回值)。Session依据该Cookie来识别是否为同一用户。
  • Session的使用比Cookie方便,但是过多的Session存储在服务器内存中,会对服务器造成压力,特别是当前服务化的应用,对于简单的内存型的Session已经不能够满足我们的要求,Session一致性问题就会有很多的问题,Spring Session提供了解决的方案。Session的一致性问题,在昨天的课堂中讲解了四种方法,(1)基于IP机制的负载均衡,各自维护自己的session.(2)服务器session复制,可以通过Tomact配置集群的方式配置同步session。(3)Session 统一缓存,这个就是spring seesion解决方案。(4)客户端缓存,也就是token机制关于APP token验证的疑问?。

spring session 简单的了解_第1张图片
spring session 简单的了解_第2张图片
spring session 简单的了解_第3张图片
spring session 简单的了解_第4张图片

spring session redis配置

//maven依赖
<dependency>
  <groupId>org.springframework.sessiongroupId>
  <artifactId>spring-session-data-redisartifactId>
  <version>1.2.1.RELEASEversion>
dependency>
<dependency>
  <groupId>redis.clientsgroupId>
  <artifactId>jedisartifactId>
  <version>2.8.1version>
dependency>

//mvc.xml 
<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>

//web.xml spring session基于拦截器处理
<filter>
    <filter-name>springSessionRepositoryFilterfilter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
filter>
<filter-mapping>
    <filter-name>springSessionRepositoryFilterfilter-name>
    <url-pattern>/*url-pattern>
filter-mapping>

在看源码之前对于Spring的一些知识点进行学习

@Bean 放置在方法之上,方法的名称就相当于之前我们定义在xml中的id

@Service
public class BeanTest {


    @Bean
    public   BeanTest  getBean(){
        BeanTest bean = new  BeanTest();
        System.out.println("调用方法:"+bean);
        return bean;
    }

}

public class Main {
    @SuppressWarnings("unused")
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");

        Object bean1 = context.getBean("getBean");

        System.out.println(bean1);
        Object bean2 = context.getBean("getBean");
        System.out.println(bean2);
    }
}

注解@import配合@Configuration,放置在注解上,可以通过实现importAware接口
通过注释可以知道,我们可以通过在Import的类中读取到注解上的相关的信息。


/**
 * Interface to be implemented by any @{@link Configuration} class that wishes
 * to be injected with the {@link AnnotationMetadata} of the @{@code Configuration}
 * class that imported it. Useful in conjunction with annotations that
 * use @{@link Import} as a meta-annotation.
 *
 * @author Chris Beams
 * @since 3.1
 */
public interface ImportAware extends Aware {

    /**
     * Set the annotation metadata of the importing @{@code Configuration} class.
     */
    void setImportMetadata(AnnotationMetadata importMetadata);

}

spring session 简单的了解_第5张图片

看源码先从web.xml这个配置文件说起

  • 配置文件中,org.springframework.web.filter.DelegatingFilterProxy是个代理类,不是真正的Filter的实现者,是通过Bean的名称获取spring ioc中的Bean,然后调用的,所以这里配置的Bean的名称可不是随便可以乱改的。如下先找到spring mvc中的Bean的名称,然后找到那个springSessionRepositoryFilter这个Bean的名称,然后在进行调用,为什么spring这么处理,为了减少不同的存储带来的差异性,进行统一的抽象,至于那个Bean在哪里定义的,细细道来。
@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        // Lazily initialize the delegate if necessary.
        Filter delegateToUse = this.delegate;
        if (delegateToUse == null) {
            synchronized (this.delegateMonitor) {
                if (this.delegate == null) {
                    WebApplicationContext wac = findWebApplicationContext();
                    if (wac == null) {
                        throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
                    }
                    this.delegate = initDelegate(wac);
                }
                delegateToUse = this.delegate;
            }
        }

        // Let the delegate perform the actual doFilter operation.
        invokeDelegate(delegateToUse, request, response, filterChain);
    }

spring session 简单的了解_第6张图片

  • 处理这个web.xml的这个配置之外,我们还要配置一个RedisHttpSessionConfiguration,或者直接使用注解的信息@EnableRedisHttpSession,EnableRedisHttpSession这个注解配合@Configuration,然后RedisHttpSessionConfiguration.class实现了importAware这个接口,通过传递函数的方式读取到注解中的值,然后反应到具体的Bean实例中去,所以可以通过注解一样的实现xml一样的配置,和其他的Aware一样的。
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({ java.lang.annotation.ElementType.TYPE })
@Documented
@Import(RedisHttpSessionConfiguration.class)
@Configuration
public @interface EnableRedisHttpSession {
    int maxInactiveIntervalInSeconds() default 1800;

    /**
     * 

* Defines a unique namespace for keys. The value is used to isolate sessions by * changing the prefix from "spring:session:" to * "spring:session:<redisNamespace>:". The default is "" such that all Redis * keys begin with "spring:session". *

* *

* For example, if you had an application named "Application A" that needed to keep * the sessions isolated from "Application B" you could set two different values for * the applications and they could function within the same Redis instance. *

* * @return the unique namespace for keys */ String redisNamespace() default ""; /** *

* Sets the flush mode for the Redis sessions. The default is ON_SAVE which only * updates the backing Redis when * {@link SessionRepository#save(org.springframework.session.Session)} is invoked. In * a web environment this happens just before the HTTP response is committed. *

*

* Setting the value to IMMEDIATE will ensure that the any updates to the Session are * immediately written to the Redis instance. *

* * @return the {@link RedisFlushMode} to use * @since 1.1 */ RedisFlushMode redisFlushMode() default RedisFlushMode.ON_SAVE; }

这个就是RedisHttpSessionConfiguration中对于importAware的实现,获取对应的注解的值,如果没有通过注解实现这个,不会去自动的调用这个方法的!

public void setImportMetadata(AnnotationMetadata importMetadata) {

        Map enableAttrMap = importMetadata
                .getAnnotationAttributes(EnableRedisHttpSession.class.getName());
        AnnotationAttributes enableAttrs = AnnotationAttributes.fromMap(enableAttrMap);
        this.maxInactiveIntervalInSeconds = enableAttrs
                .getNumber("maxInactiveIntervalInSeconds");
        this.redisNamespace = enableAttrs.getString("redisNamespace");
        this.redisFlushMode = enableAttrs.getEnum("redisFlushMode");
    }
  • RedisHttpSessionConfiguration这个类中,刚刚开始我一直在找那个web.xml中配置的那个Bean的名称springSessionRepositoryFilter,发现没有找到,除了一些spring订阅接受者和spring redistemplate,SessionRepository session的保存工程等等配置。莫急,折腾了一会吃了饭之后在去看,发现其实我没有理解一个原理,@Bean没有定义Name那么这个Bean的name默认为这个方法的名称,在其父类中发现了这个配置,类似的其他的储存(MongoDB)都是继承了这个父类SpringHttpSessionConfiguration中发现了这个方法,这个方法会依赖一个SessionRepository session的存储容器,比如redis或者Map等等,spring 提供了很多的实现方法,还有设置了session策越是将session的唯一标识放置在Cookie还是放置在Head头中,这里默认是Cookie这种方式。具体依赖哪个存储工厂,这个就是子类的事情了,比如redis配置工厂。
@Bean
    public  SessionRepositoryFilter springSessionRepositoryFilter(
            SessionRepository sessionRepository) {
        SessionRepositoryFilter sessionRepositoryFilter = new SessionRepositoryFilter(
                sessionRepository);
        sessionRepositoryFilter.setServletContext(this.servletContext);
        if (this.httpSessionStrategy instanceof MultiHttpSessionStrategy) {
            sessionRepositoryFilter.setHttpSessionStrategy(
                    (MultiHttpSessionStrategy) this.httpSessionStrategy);
        }
        else {
            sessionRepositoryFilter.setHttpSessionStrategy(this.httpSessionStrategy);
        }
        return sessionRepositoryFilter;
    }

完整源码,还有监听seesion的变化List可以自己手动的配置

public class SpringHttpSessionConfiguration {

    private CookieHttpSessionStrategy defaultHttpSessionStrategy = new CookieHttpSessionStrategy();

    private HttpSessionStrategy httpSessionStrategy = this.defaultHttpSessionStrategy;

    private List httpSessionListeners = new ArrayList();

    private ServletContext servletContext;

    @Bean
    public SessionEventHttpSessionListenerAdapter sessionEventHttpSessionListenerAdapter() {
        return new SessionEventHttpSessionListenerAdapter(this.httpSessionListeners);
    }

    @Bean
    public  SessionRepositoryFilter springSessionRepositoryFilter(
            SessionRepository sessionRepository) {
        SessionRepositoryFilter sessionRepositoryFilter = new SessionRepositoryFilter(
                sessionRepository);
        sessionRepositoryFilter.setServletContext(this.servletContext);
        if (this.httpSessionStrategy instanceof MultiHttpSessionStrategy) {
            sessionRepositoryFilter.setHttpSessionStrategy(
                    (MultiHttpSessionStrategy) this.httpSessionStrategy);
        }
        else {
            sessionRepositoryFilter.setHttpSessionStrategy(this.httpSessionStrategy);
        }
        return sessionRepositoryFilter;
    }

    @Autowired(required = false)
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    @Autowired(required = false)
    public void setCookieSerializer(CookieSerializer cookieSerializer) {
        this.defaultHttpSessionStrategy.setCookieSerializer(cookieSerializer);
    }

    @Autowired(required = false)
    public void setHttpSessionStrategy(HttpSessionStrategy httpSessionStrategy) {
        this.httpSessionStrategy = httpSessionStrategy;
    }

    @Autowired(required = false)
    public void setHttpSessionListeners(List listeners) {
        this.httpSessionListeners = listeners;
    }
}

Redis的完整源码

@Configuration
@EnableScheduling
public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration
        implements ImportAware {

    private Integer maxInactiveIntervalInSeconds = 1800;

    private ConfigureRedisAction configureRedisAction = new ConfigureNotifyKeyspaceEventsAction();

    private String redisNamespace = "";

    private RedisFlushMode redisFlushMode = RedisFlushMode.ON_SAVE;

    private RedisSerializer defaultRedisSerializer;

 //这里是监听Redis数据过期和数据删除的通知
    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(
            RedisConnectionFactory connectionFactory,
            RedisOperationsSessionRepository messageListener) {

        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        if (this.redisTaskExecutor != null) {
            container.setTaskExecutor(this.redisTaskExecutor);
        }
        if (this.redisSubscriptionExecutor != null) {
            container.setSubscriptionExecutor(this.redisSubscriptionExecutor);
        }
        container.addMessageListener(messageListener,
                Arrays.asList(new PatternTopic("__keyevent@*:del"),
                        new PatternTopic("__keyevent@*:expired")));
        container.addMessageListener(messageListener, Arrays.asList(new PatternTopic(
                messageListener.getSessionCreatedChannelPrefix() + "*")));
        return container;
    }

  //Redis 操作类
    @Bean
    public RedisTemplate sessionRedisTemplate(
            RedisConnectionFactory connectionFactory) {
        RedisTemplate template = new RedisTemplate();
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        if (this.defaultRedisSerializer != null) {
            template.setDefaultSerializer(this.defaultRedisSerializer);
        }
        template.setConnectionFactory(connectionFactory);
        return template;
    }

    //Redis的仓库,处理业务中保存Session的操作,也是父类中需要注入到SessionRepositoryFilter中的数据信息
    @Bean
    public RedisOperationsSessionRepository sessionRepository(
            @Qualifier("sessionRedisTemplate") RedisOperations sessionRedisTemplate,
            ApplicationEventPublisher applicationEventPublisher) {
        RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(
                sessionRedisTemplate);
        sessionRepository.setApplicationEventPublisher(applicationEventPublisher);
        sessionRepository
                .setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
        if (this.defaultRedisSerializer != null) {
            sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
        }

        String redisNamespace = getRedisNamespace();
        if (StringUtils.hasText(redisNamespace)) {
            sessionRepository.setRedisKeyNamespace(redisNamespace);
        }

        sessionRepository.setRedisFlushMode(this.redisFlushMode);
        return sessionRepository;
    }

    public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
        this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
    }

    public void setRedisNamespace(String namespace) {
        this.redisNamespace = namespace;
    }

    public void setRedisFlushMode(RedisFlushMode redisFlushMode) {
        Assert.notNull(redisFlushMode, "redisFlushMode cannot be null");
        this.redisFlushMode = redisFlushMode;
    }

    private String getRedisNamespace() {
        if (StringUtils.hasText(this.redisNamespace)) {
            return this.redisNamespace;
        }
        return System.getProperty("spring.session.redis.namespace", "");
    }

    public void setImportMetadata(AnnotationMetadata importMetadata) {

        Map enableAttrMap = importMetadata
                .getAnnotationAttributes(EnableRedisHttpSession.class.getName());
        AnnotationAttributes enableAttrs = AnnotationAttributes.fromMap(enableAttrMap);
        this.maxInactiveIntervalInSeconds = enableAttrs
                .getNumber("maxInactiveIntervalInSeconds");
        this.redisNamespace = enableAttrs.getString("redisNamespace");
        this.redisFlushMode = enableAttrs.getEnum("redisFlushMode");
    }
} 
  

SessionRepositoryFilter 这个过滤器,必须放在第一个,这个样才能替换掉原始的HTTPSession的默认的实现

HttpSession是一个规范,可以替换他的默认的实现,包装HttpServletRequest,让他请求原始的getSession的时候覆盖掉默认的实现,比如我们可以使用Redis仓库去实现这个机制,HttpServletRequestWrapper我们只需要继承在这个包装类就可以覆盖掉自己需要覆盖的部分,那么获取session的时候默认的使用我们包装的HttpServletRequest,不再是请求原始的那个了,过滤链传递完成后所有的请求都使用这个引用啦!然后在使用我们的redis参考处理存储,删除等操作就好了。

包装HttpServletRequest

/**
     * A {@link javax.servlet.http.HttpServletRequest} that retrieves the
     * {@link javax.servlet.http.HttpSession} using a
     * {@link org.springframework.session.SessionRepository}.
     *
     * @author Rob Winch
     * @since 1.0
     */
    private final class SessionRepositoryRequestWrapper
            extends HttpServletRequestWrapper {
        private final String CURRENT_SESSION_ATTR = HttpServletRequestWrapper.class.getName();
        private Boolean requestedSessionIdValid;
        private boolean requestedSessionInvalidated;
        private final HttpServletResponse response;
        private final ServletContext servletContext;

        private SessionRepositoryRequestWrapper(HttpServletRequest request,
                HttpServletResponse response, ServletContext servletContext) {
            super(request);
            this.response = response;
            this.servletContext = servletContext;
        }

        /**
         * Uses the HttpSessionStrategy to write the session id to the response and
         * persist the Session.
         */
        private void commitSession() {
            HttpSessionWrapper wrappedSession = getCurrentSession();
            if (wrappedSession == null) {
                if (isInvalidateClientSession()) {
                    SessionRepositoryFilter.this.httpSessionStrategy
                            .onInvalidateSession(this, this.response);
                }
            }
            else {
                S session = wrappedSession.getSession();
                SessionRepositoryFilter.this.sessionRepository.save(session);
                if (!isRequestedSessionIdValid()
                        || !session.getId().equals(getRequestedSessionId())) {
                    SessionRepositoryFilter.this.httpSessionStrategy.onNewSession(session,
                            this, this.response);
                }
            }
        }

        @SuppressWarnings("unchecked")
        private HttpSessionWrapper getCurrentSession() {
            return (HttpSessionWrapper) getAttribute(this.CURRENT_SESSION_ATTR);
        }

        private void setCurrentSession(HttpSessionWrapper currentSession) {
            if (currentSession == null) {
                removeAttribute(this.CURRENT_SESSION_ATTR);
            }
            else {
                setAttribute(this.CURRENT_SESSION_ATTR, currentSession);//将当前的session保存在一个属性中!
            }
        }

        @SuppressWarnings("unused")
        public String changeSessionId() {
            HttpSession session = getSession(false);

            if (session == null) {
                throw new IllegalStateException(
                        "Cannot change session ID. There is no session associated with this request.");
            }

            // eagerly get session attributes in case implementation lazily loads them
            Map attrs = new HashMap();
            Enumeration iAttrNames = session.getAttributeNames();
            while (iAttrNames.hasMoreElements()) {
                String attrName = iAttrNames.nextElement();
                Object value = session.getAttribute(attrName);

                attrs.put(attrName, value);
            }

            SessionRepositoryFilter.this.sessionRepository.delete(session.getId());
            HttpSessionWrapper original = getCurrentSession();
            setCurrentSession(null);

            HttpSessionWrapper newSession = getSession();
            original.setSession(newSession.getSession());

            newSession.setMaxInactiveInterval(session.getMaxInactiveInterval());
            for (Map.Entry attr : attrs.entrySet()) {
                String attrName = attr.getKey();
                Object attrValue = attr.getValue();
                newSession.setAttribute(attrName, attrValue);
            }
            return newSession.getId();
        }

        @Override
        public boolean isRequestedSessionIdValid() {
            if (this.requestedSessionIdValid == null) {
                String sessionId = getRequestedSessionId();
                S session = sessionId == null ? null : getSession(sessionId);
                return isRequestedSessionIdValid(session);
            }

            return this.requestedSessionIdValid;
        }

        private boolean isRequestedSessionIdValid(S session) {
            if (this.requestedSessionIdValid == null) {
                this.requestedSessionIdValid = session != null;
            }
            return this.requestedSessionIdValid;
        }

        private boolean isInvalidateClientSession() {
            return getCurrentSession() == null && this.requestedSessionInvalidated;
        }
        //通过sessionId 从仓库中获取session,并更新访问的时间
        private S getSession(String sessionId) {
            S session = SessionRepositoryFilter.this.sessionRepository
                    .getSession(sessionId);
            if (session == null) {
                return null;
            }
            session.setLastAccessedTime(System.currentTimeMillis());
            return session;
        }
        //覆盖掉父类的方法
        @Override
        public HttpSessionWrapper getSession(boolean create) {
            //包装Session添加其他的功能,看看当前是否有session
            HttpSessionWrapper currentSession = getCurrentSession();
            if (currentSession != null) {
                return currentSession;
            }
            //通过当前的Request 中cookie中获取seesionid 获取session
            String requestedSessionId = getRequestedSessionId();
            if (requestedSessionId != null
                    && getAttribute(INVALID_SESSION_ID_ATTR) == null) {
                S session = getSession(requestedSessionId);
                if (session != null) {
                    //当前的Session是有效的,然后在进行包装一下,防止当前没有经过包装
                    this.requestedSessionIdValid = true;
                    currentSession = new HttpSessionWrapper(session, getServletContext());
                    currentSession.setNew(false);
                    setCurrentSession(currentSession);
                    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.");
                    }
                    //当前这个是非法的session
                    setAttribute(INVALID_SESSION_ID_ATTR, "true");
                }
            }
            //不创建新的Session
            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)"));
            }
            S session = SessionRepositoryFilter.this.sessionRepository.createSession();//从窗仓库中创建一个Session
            session.setLastAccessedTime(System.currentTimeMillis());
            currentSession = new HttpSessionWrapper(session, getServletContext());
            setCurrentSession(currentSession);
            return currentSession;
        }

        @Override
        public ServletContext getServletContext() {
            if (this.servletContext != null) {
                return this.servletContext;
            }
            // Servlet 3.0+
            return super.getServletContext();
        }

        @Override
        public HttpSessionWrapper getSession() {
            return getSession(true);
        }

        @Override
        public String getRequestedSessionId() {
            return SessionRepositoryFilter.this.httpSessionStrategy
                    .getRequestedSessionId(this);
        }

        /**
         * Allows creating an HttpSession from a Session instance.
         *
         * @author Rob Winch
         * @since 1.0
         */
        //这里对于session进行包装,添加ServletContext的引用,ServletContext需要web的环境。
        private final class HttpSessionWrapper extends ExpiringSessionHttpSession<S> {

            HttpSessionWrapper(S session, ServletContext servletContext) {
                super(session, servletContext);
            }

            @Override
            public void invalidate() {
                //删除掉Session
                super.invalidate();
                SessionRepositoryRequestWrapper.this.requestedSessionInvalidated = true;
                setCurrentSession(null);
                SessionRepositoryFilter.this.sessionRepository.delete(getId());
            }
        }
    }

包装HttpServletResponseWrapper->OnCommittedResponseWrapper(包装当提交数据的时候做一些监听的操作)->SessionRepositoryResponseWrapper这个包装的意思就是返回值的时候做一些操作哦!

private final class SessionRepositoryResponseWrapper
            extends OnCommittedResponseWrapper {

        private final SessionRepositoryRequestWrapper request;

        /**
         * Create a new {@link SessionRepositoryResponseWrapper}.
         * @param request the request to be wrapped
         * @param response the response to be wrapped
         */
        SessionRepositoryResponseWrapper(SessionRepositoryRequestWrapper request,
                HttpServletResponse response) {
            super(response);
            if (request == null) {
                throw new IllegalArgumentException("request cannot be null");
            }
            this.request = request;
        }
        //这个就是上层包装处理,触发提交session的操作,第一个就是保存数据库第二个就是返回Cookie
        @Override
        protected void onResponseCommitted() {
            this.request.commitSession();
        }
    }

下面就是主要的流程啦!包装,然后传递给下面的过滤链,使用的就是我们包装过的Session,这里不是原始的Filter的过滤链,是实现了只过滤一次的过滤链,所以调用的方法不一样


    @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();
        }
    }

这个Filter是之前继承的Filter这里的意思就是只被处触发一次,平时可以使用一次下

/**
 * Allows for easily ensuring that a request is only invoked once per request. This is a
 * simplified version of spring-web's OncePerRequestFilter and copied to reduce the foot
 * print required to use the session support.
 *
 * @author Rob Winch
 * @since 1.0
 */
abstract class OncePerRequestFilter implements Filter {
    /**
     * Suffix that gets appended to the filter name for the "already filtered" request
     * attribute.
     */
    public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";

    private String alreadyFilteredAttributeName = getClass().getName()
            .concat(ALREADY_FILTERED_SUFFIX);

    /**
     * This {@code doFilter} implementation stores a request attribute for
     * "already filtered", proceeding without filtering again if the attribute is already
     * there.
     * @param request the request
     * @param response the response
     * @param filterChain the filter chain
     * @throws ServletException if request is not HTTP request
     * @throws IOException in case of I/O operation exception
     */
    public final void doFilter(ServletRequest request, ServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {

        if (!(request instanceof HttpServletRequest)
                || !(response instanceof HttpServletResponse)) {
            throw new ServletException(
                    "OncePerRequestFilter just supports HTTP requests");
        }
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        boolean hasAlreadyFilteredAttribute = request
                .getAttribute(this.alreadyFilteredAttributeName) != null;

        if (hasAlreadyFilteredAttribute) {

            // Proceed without invoking this filter...
            filterChain.doFilter(request, response);
        }
        else {
            // Do invoke this filter...
            request.setAttribute(this.alreadyFilteredAttributeName, Boolean.TRUE);
            try {
                doFilterInternal(httpRequest, httpResponse, filterChain);
            }
            finally {
                // Remove the "already filtered" request attribute for this request.
                request.removeAttribute(this.alreadyFilteredAttributeName);
            }
        }
    }

    /**
     * Same contract as for {@code doFilter}, but guaranteed to be just invoked once per
     * request within a single request thread.
     * 

* Provides HttpServletRequest and HttpServletResponse arguments instead of the * default ServletRequest and ServletResponse ones. * * @param request the request * @param response the response * @param filterChain the FilterChain * @throws ServletException thrown when a non-I/O exception has occurred * @throws IOException thrown when an I/O exception of some sort has occurred * @see Filter#doFilter */ protected abstract void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException; public void init(FilterConfig config) { } public void destroy() { } }

看这个Filter的头写的很详细,HttpSession被org.springframework.session.Session支持,这样的支持是通过包装类HttpSessionWrapper ->ExpiringSessionHttpSession->HttpSession
保存被Session仓库 SessionRepository 所支持。The SessionRepositoryFilter must be placed before any Filter that access the


/**
 * Switches the {@link javax.servlet.http.HttpSession} implementation to be backed by a
 * {@link org.springframework.session.Session}.
 *
 * The {@link SessionRepositoryFilter} wraps the
 * {@link javax.servlet.http.HttpServletRequest} and overrides the methods to get an
 * {@link javax.servlet.http.HttpSession} to be backed by a
 * {@link org.springframework.session.Session} returned by the
 * {@link org.springframework.session.SessionRepository}.
 *
 * The {@link SessionRepositoryFilter} uses a {@link HttpSessionStrategy} (default
 * {@link CookieHttpSessionStrategy} to bridge logic between an
 * {@link javax.servlet.http.HttpSession} and the
 * {@link org.springframework.session.Session} abstraction. Specifically:
 *
 * 
    *
  • The session id is looked up using * {@link HttpSessionStrategy#getRequestedSessionId(javax.servlet.http.HttpServletRequest)} * . The default is to look in a cookie named SESSION.
  • *
  • The session id of newly created {@link org.springframework.session.ExpiringSession} * is sent to the client using *
  • The client is notified that the session id is no longer valid with * {@link HttpSessionStrategy#onInvalidateSession(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)} *
  • *
* *

* The SessionRepositoryFilter must be placed before any Filter that access the * HttpSession or that might commit the response to ensure the session is overridden and * persisted properly. *

* * @param the {@link ExpiringSession} type. * @since 1.0 * @author Rob Winch */

spring session 简单的了解_第7张图片

你可能感兴趣的:(J2EE)