Spring-Session源码研究之Start
来源:互联网 发布:r2v矢量化软件 编辑:程序博客网 时间:2024/06/03 15:10
粗浅的源码阅读
2017/11/8 加入更加详细的说明, 重新整理格式.
0. 总结
- web.xml 的加载顺序是:
context-param
->listener
->filter
->servlet
- 所以按照以上的加载顺序, 我们在web.xml中声明的
ContextLoaderListener
的Listener
以及相呼应的名为contextConfigLocation
的<context-param>
就会先于我们在web.xml
声明的springSessionRepositoryFilter
之前加载,构建出springMVC中的首个WebApplicationContext
容器(由spring.xml定义). - 上面的
ContextLoaderListener
在构建Spring容器时, 会扫描spring-session
包中以编程方式声明的配置(RedisHttpSessionConfiguration
和SpringHttpSessionConfiguration
等配置类), 从而将springSessionRepositoryFilter
(类型为SessionRepositoryFilter<>
)等运行必须的实例注入到SpringMVC容器(由spring.xml定义)中。 - 接着
web.xml
中的名为springSessionRepositoryFilter
的DelegatingFilterProxy
类型初始化(通过获取并使用上面构建出的容器(spring.xml
定义)里的必要实例), 实例化出操作redis的客户端实例. - 因为我们声明的
springSessionRepositoryFilter
是作为第一个Filter
, 于是在之后的每次请求中都会率先调用该Filter
, 在其中将Servlet容器(例如Tomcat)生成的request,response实例包装成自定义的request,response实例. 这样就透明化session的分布式存储. - 因为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);}
- 然后再回到
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
中
- 现在让我们回头看看我们在
spring.xml
中声明的其中一个bean:RedisHttpSessionConfiguration
, 通过查看其源码, 我们发现其被@Configuration
注解所修饰. - 然后其基类
SpringHttpSessionConfiguration
,也是被@Configuration
注解所修饰, 而且这个基类内部正好就有一个被@Bean
注解所修饰的springSessionRepositoryFilter
方法, - 到此,这两者就串联起来了.
3. 注意
- 构建
SessionRepositoryFilter
类型的springSessionRepositoryFilter
Bean所需要的依赖项SessionRepository<S>
类型的sessionRepository
, 是在RedisHttpSessionConfiguration
中构建的. - 而构建
sessionRepository
所需要的connectionFactory
, 则正是我们在spring.xml
中定义的. - 依赖关系是:
SessionRepositoryFilter
类型的springSessionRepositoryFilter
Bean(就是它作为第一个Filter,SpringHttpSessionConfiguration
中声明) –>SessionRepository
类型的sessionRepository
Bean(被作为SessionRepositoryFilter
类型的构造函数的参数依赖,RedisHttpSessionConfiguration
中声明. ) –>RedisTemplate
类型的sessionRedisTemplate
Bean,(作为构造`sessionRepository
Bean的必备参数,RedisHttpSessionConfiguration
中声明) –>RedisConnectionFactory
类型的JedisConnectionFactory
(其实现了RedisConnectionFactory
接口) Bean (作为构造sessionRedisTemplate
Bean的必备参数,spring.xml
中声明)
上面的第3步中, 我们反向看过去就能构建出一个
springSessionRepositoryFilter
.这里偶然看到了
SpringHttpSessionConfiguration
类上的注释的@EnableSpringHttpSession
注解, 其可以简化springSessionRepositoryFilter
的创建. 而且可以发现一个规律: 一般Spring中名为EnableXxxx
, 其定义基本都有一个@Import( )
的注解, 其实也很好理解, 就像Spring在使用xml配置时代, 使用某个标签来引入某个类来开启某个功能; 这里也是一样的道理, 通过引入@Import( )
注解引入某个类来开启声明的功能.
3. 后话
- 所以其实Spring-Session的生效是从
web.xml
中的Filter启动开始的, 而该Filter正是定义在spring-session-1.2.0.RELEASE.jar
中的SessionRepositoryFilter<S extends ExpiringSession>
.关于该Filter如何生效我们留到下一篇博客. - 那么我们就可以通过注释掉web.xml中的springSessionRepositoryFilter声明来进行日常的测试环境.
4. Links
- 官方网站
- spring-session使用配置(分布式共享session配置) – 配置相关
- 利用spring session解决共享Session问题 – 有原理分析
- http://blog.csdn.net/szwandcj/article/details/50319901
- http://blog.csdn.net/wojiaolinaaa/article/details/62424642
阅读全文
0 0
- Spring-Session源码研究之Start
- Spring-Session源码研究之processRequest
- Spring-Session源码研究之Start_Servlet3.0
- SpringBoot源码研究之Start
- Spring源码研究之@Configuration
- spring源码研究之路_IOC
- spring源码研究(0)
- spring-session源码解析
- Spring源码研究之注解扫描<context:component-scan/>
- Mybatis与Spring集成源码研究之MapperScannerConfigurer
- ViewState与Session之研究
- ViewState与Session之研究
- Spring AOP源码研究笔记
- 研究spring源码有所得
- spring-session源码解读-1
- spring-session源码解读-2
- spring-session源码解读-3
- spring-session源码解读-4
- 10月30日解题报告
- 逻辑分析仪抓取波形参数设置
- 27_反射
- EasyUI+Ajax+Json+一般处理程序实现数据的前台与后台的交互
- 顺序查找及C++代码实现SequentialSearch
- Spring-Session源码研究之Start
- Android设计模式之——模板方法模式
- 压缩图片
- 实验四顺序栈
- HashMap 在JDK1.7中的实现原理分析
- LDAP入门学习
- JavaWeb学习笔记-java基础-5-可变参数
- java IO 字符流之 Buffered缓冲区
- Eclipse集成Maven和Scala