Spring-Session源码研究之Start

来源:互联网 发布:r2v矢量化软件 编辑:程序博客网 时间:2024/06/03 15:10

粗浅的源码阅读

2017/11/8 加入更加详细的说明, 重新整理格式.

0. 总结

  1. web.xml 的加载顺序是: context-param -> listener -> filter -> servlet
  2. 所以按照以上的加载顺序, 我们在web.xml中声明的ContextLoaderListenerListener以及相呼应的名为contextConfigLocation<context-param>就会先于我们在web.xml声明的springSessionRepositoryFilter之前加载,构建出springMVC中的首个WebApplicationContext容器(由spring.xml定义).
  3. 上面的ContextLoaderListener在构建Spring容器时, 会扫描spring-session包中以编程方式声明的配置(RedisHttpSessionConfigurationSpringHttpSessionConfiguration等配置类), 从而将springSessionRepositoryFilter(类型为SessionRepositoryFilter<>)等运行必须的实例注入到SpringMVC容器(由spring.xml定义)中。
  4. 接着web.xml中的名为springSessionRepositoryFilterDelegatingFilterProxy类型初始化(通过获取并使用上面构建出的容器(spring.xml定义)里的必要实例), 实例化出操作redis的客户端实例.
  5. 因为我们声明的springSessionRepositoryFilter是作为第一个Filter, 于是在之后的每次请求中都会率先调用该Filter, 在其中将Servlet容器(例如Tomcat)生成的request,response实例包装成自定义的request,response实例. 这样就透明化session的分布式存储.
  6. 因为Servlet3.0之后, 可以在容器启动阶段通过编程风格注册Filter, Servlet以及Listener. 所以区别仅仅是利用新增的ServletContainerInitializer将在以上在web.xml中的注册使用编码的方式完成, 脱离与Servlet容器的依赖关系. 之后的逻辑完全一致.

1. 配置

1.1 Maven

<dependency>      <groupId>org.springframework.session</groupId>      <artifactId>spring-session-data-redis</artifactId>      <version>1.2.0.RELEASE</version>  </dependency>

1.2 Spring.xml

这个Spring.xml必须是在web.xml中的名为contextConfigLocation<context-param>进行指定

<!-- 将session放入redis --><!--如果配置了 <context:component-scan/> 就不需要再配置下面这个了    这个注解最终就会导致下面的@Configuration注解被解析, 将@Bean等注解被解析注入到容器中.    该注解在哪个配置文件, 上面这些bean就会被注入到该配置文件构造的容器中.--><context:annotation-config/><!-- RedisHttpSessionConfiguration类上有注解@Configuration; 其基类SpringHttpSessionConfiguration类上也是有@Configuration注解的,而其内部就有一个被@Bean所标注的springSessionRepositoryFilter方法。--><bean id="redisHttpSessionConfiguration"  class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration" >    <!-- Seesion的有效期限; 以秒为单位; 默认是1800秒(30 minutes); 注意这个默认值的定义位置位于 GemFireHttpSessionConfiguration 类中(DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS字段) -->            <property name="maxInactiveIntervalInSeconds" value="120" /> </bean><bean  class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">    <!-- redis 配置 -->    <property name="hostName" value="192.168.0.48" />    <property name="port" value="6379" /></bean>

1.3 web.xml

<!--上面的spring.xml必须声明在这里 --><context-param>    <param-name>contextConfigLocation</param-name>    <param-value>        classpath:config/spring.xml;    </param-value></context-param><!-- 分布式Session共享Filter,保证是第一个Filter --><!-- 1.  web.xml 的加载顺序是:context-param -> listener -> filter -> servlet ,     2. 而同个类型之间的实际程序调用的时候的顺序是根据对应的 mapping 出现的顺序进行调用的。     3. 这里的名称一定要是springSessionRepositoryFilter --><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>

2. 解读

2.1 DelegatingFilterProxy

  • 先从我们在web.xml中注册的DelegatingFilterProxy开始,此类定义在spring-mvc-xxx.jar中.
  • DelegatingFilterProxy类直接继承自GenericFilterBean, 而GenericFilterBean直接实现了javax.servlet.Filter接口, 并且将javax.servlet.Filter接口的init方法final化, 并将扩展的途径以自定义的initFilterBean方法暴露给子类, 这样GenericFilterBean就能够将重复性的代码进行封装.
// 无关代码进行了删除, 节省篇幅public final void init(FilterConfig filterConfig) throws ServletException {    this.filterConfig = filterConfig;    // Set bean properties from init parameters.    try {        // FilterConfigPropertyValues类继承自MutablePropertyValues        // 这个技巧和Mybatis中的SqlSourceBuilder.ParameterMappingTokenHandler非常类似(http://blog.csdn.net/lqzkcx3/article/details/78370567第七小结). 将解析逻辑封装起来, 对外暴露获取键值对的接口.        // 这里FilterConfigPropertyValues的逻辑主要是将通过web.xml传入的Filter初始化参数进行解析为一个个PropertyValue, 并对必要参数进行验证.        PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);        ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());        // 注册针对Resouce的Editor        bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));        // 空实现, 留给子类.        initBeanWrapper(bw);        bw.setPropertyValues(pvs, true);    }    catch (BeansException ex) {        String msg = "Failed to set bean properties on filter '" +            filterConfig.getFilterName() + "': " + ex.getMessage();        logger.error(msg, ex);        throw new NestedServletException(msg, ex);    }    // Let subclasses do whatever initialization they like.    //  交给子类去实现自定义的逻辑,该方法自身实现为空    initFilterBean();}
  • 再观察GenericFilterBean类的getFilterName方法就会发现, 其一般情况下返回的就是本Filter的名称(即在web.xml中配置的).
protected final String getFilterName() {    // 在web.xml定义的名称(springSessionRepositoryFilter)优先级高    return (this.filterConfig != null ? this.filterConfig.getFilterName() : this.beanName);}
  1. 然后再回到DelegatingFilterProxy覆写了的initFilterBean方法中, 就会发现其为targetBeanName字段的赋值就是来源于上面这个方法.
@Overrideprotected void initFilterBean() throws ServletException {    synchronized (this.delegateMonitor) {        if (this.delegate == null) {            // If no target bean name specified, use filter name.            // 按照上面我们在web.xml中的定义, 这个targetBeanName的类肯定为null.            if (this.targetBeanName == null) {                // 所以这里就会将我们在web.xml中定义的名称(springSessionRepositoryFilter)赋值给targetBeanName.                this.targetBeanName = getFilterName();            }            // Fetch Spring root application context and initialize the delegate early,            // if possible. If the root application context will be started after this            // filter proxy, we'll have to resort to lazy initialization.            // findWebApplicationContext方法的实现我就不贴了,            // 这里我们将找到我们使用spring.xml定义的那个WebApplicationContext容器.            WebApplicationContext wac = findWebApplicationContext();            if (wac != null) {                this.delegate = initDelegate(wac);            }        }    }}protected Filter initDelegate(WebApplicationContext wac) throws ServletException {    // 记住这里    // 1. getTargetBeanName()返回值就是我们在web.xml中定义的"springSessionRepositoryFilter",     // 2. wac是由spring.xml定义的那个WebApplicationContext容器.    Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);    if (isTargetFilterLifecycle()) {        delegate.init(getFilterConfig());    }    return delegate;}

2.2 springSessionRepositoryFilter从何而来

现在的问题就剩这个名为springSessionRepositoryFilter的Bean我们是在哪里定义的? 而且请注意这个bean必须在上面说的spring.xml

  1. 现在让我们回头看看我们在spring.xml中声明的其中一个bean: RedisHttpSessionConfiguration, 通过查看其源码, 我们发现其被@Configuration注解所修饰.
  2. 然后其基类SpringHttpSessionConfiguration,也是被@Configuration注解所修饰, 而且这个基类内部正好就有一个被@Bean注解所修饰的springSessionRepositoryFilter方法,
  3. 到此,这两者就串联起来了.

3. 注意

  1. 构建SessionRepositoryFilter类型的springSessionRepositoryFilter Bean所需要的依赖项SessionRepository<S>类型的 sessionRepository, 是在RedisHttpSessionConfiguration中构建的.
  2. 而构建 sessionRepository 所需要的 connectionFactory, 则正是我们在spring.xml中定义的.
  3. 依赖关系是:
    1. SessionRepositoryFilter类型的springSessionRepositoryFilter Bean(就是它作为第一个Filter, SpringHttpSessionConfiguration中声明) –>
    2. SessionRepository类型的sessionRepository Bean(被作为SessionRepositoryFilter类型的构造函数的参数依赖, RedisHttpSessionConfiguration中声明. ) –>
    3. RedisTemplate类型的sessionRedisTemplate Bean,(作为构造`sessionRepository Bean的必备参数, RedisHttpSessionConfiguration中声明) –>
    4. RedisConnectionFactory类型的 JedisConnectionFactory(其实现了RedisConnectionFactory接口) Bean (作为构造sessionRedisTemplate Bean的必备参数, spring.xml中声明)
  4. 上面的第3步中, 我们反向看过去就能构建出一个springSessionRepositoryFilter.

  5. 这里偶然看到了SpringHttpSessionConfiguration类上的注释的@EnableSpringHttpSession注解, 其可以简化 springSessionRepositoryFilter 的创建. 而且可以发现一个规律: 一般Spring中名为 EnableXxxx, 其定义基本都有一个@Import( )的注解, 其实也很好理解, 就像Spring在使用xml配置时代, 使用某个标签来引入某个类来开启某个功能; 这里也是一样的道理, 通过引入@Import( )注解引入某个类来开启声明的功能.

3. 后话

  1. 所以其实Spring-Session的生效是从web.xml中的Filter启动开始的, 而该Filter正是定义在spring-session-1.2.0.RELEASE.jar中的SessionRepositoryFilter<S extends ExpiringSession>.关于该Filter如何生效我们留到下一篇博客.
  2. 那么我们就可以通过注释掉web.xml中的springSessionRepositoryFilter声明来进行日常的测试环境.
  1. 官方网站
  2. spring-session使用配置(分布式共享session配置) – 配置相关
  3. 利用spring session解决共享Session问题 – 有原理分析
  4. http://blog.csdn.net/szwandcj/article/details/50319901
  5. http://blog.csdn.net/wojiaolinaaa/article/details/62424642