AbstractHttpSessionApplicationInitializer,很明显它是一个初始化的类,它是一个抽象类,可以理解为一个公用的基类,然后看一下onStartup这个方法,最主要的方法看这里,insertSessionRepositoryFilter,把servlet的一个上下文传进来了,/*** Registers the springSessionRepositoryFilter.* @param servletContext the {@link ServletContext}*/private void insertSessionRepositoryFilter(ServletContext servletContext) {String filterName = DEFAULT_FILTER_NAME;DelegatingFilterProxy springSessionRepositoryFilter = new DelegatingFilterProxy(filterName);String contextAttribute = getWebApplicationContextAttribute();if (contextAttribute != null) {springSessionRepositoryFilter.setContextAttribute(contextAttribute);}registerFilter(servletContext, true, filterName, springSessionRepositoryFilter);}DEFAULT_FILTER_NAME是什么public static final String DEFAULT_FILTER_NAME = "springSessionRepositoryFilter";DEFAULT_FILTER_NAME正是springSessionRepositoryFilter,那看到这个名字呢,还记不记得我们web.xml里面声明的,<!-- spring session框架和自己实现的redis二选一 --><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>*.do</url-pattern></filter-mapping>正是这个name,进入insertSessionRepositoryFilter这个方法,这里面动态的加载了一个DelegatingFilterProxy,这里只有一个参数,而这个参数呢,通过这个名字我们就能够看出来,这个filter最终会把请求,代理给具体的一个filter,那通过入参的常量呢,就可以看出来,它是委派给springSessionRepositoryFilter这个filter,而这个filter是由Spring容器管理的,那说到了这里我们就要看一下DelegatingFilterProxy这个类,首先这个类的包在哪个包下呢,这个包是引用Spring4.0.3,我们可以看一下他的继承关系

首先它继承了GenericFilterBean,然后GenericFilterBean又实现了以上这些接口,同时他也实现了Filter接口,所以我们可以在filter里面配置他,他的父类是一个抽象类,我们看一下他的初始化方法,/*** Standard way of initializing this filter.* Map config parameters onto bean properties of this filter, and* invoke subclass initialization.* @param filterConfig the configuration for this filter* @throws ServletException if bean properties are invalid (or required* properties are missing), or if subclass initialization fails.* @see #initFilterBean*/@Overridepublic final void init(FilterConfig filterConfig) throws ServletException {Assert.notNull(filterConfig, "FilterConfig must not be null");if (logger.isDebugEnabled()) {logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");}this.filterConfig = filterConfig;// Set bean properties from init parameters.PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);if (!pvs.isEmpty()) {try {BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());Environment env = this.environment;if (env == null) {env = new StandardServletEnvironment();}bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, env));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();if (logger.isDebugEnabled()) {logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");}}这里面有一个initFilterBean,也就是filterBean是在这里初始化的,我们可以看到这个方法是空实现/*** Subclasses may override this to perform custom initialization.* All bean properties of this filter will have been set before this* method is invoked.* <p>Note: This method will be called from standard filter initialization* as well as filter bean initialization in a Spring application context.* Filter name and ServletContext will be available in both cases.* <p>This default implementation is empty.* @throws ServletException if subclass initialization fails* @see #getFilterName()* @see #getServletContext()*/protected void initFilterBean() throws ServletException {}这个时候按CTRL+T进入到他的实现里面@Overrideprotected void initFilterBean() throws ServletException {synchronized (this.delegateMonitor) {if (this.delegate == null) {// If no target bean name specified, use filter name.if (this.targetBeanName == null) {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.WebApplicationContext wac = findWebApplicationContext();if (wac != null) {this.delegate = initDelegate(wac);}}}}这个时候就跳入到DelegatingFilterProxy这个类里面这个类上边有一个同步块,首先是为了防止Spring容器在启动的时候,保证一下他们的执行顺序,然后最后获取一下WebApplicationContext,然后再把委托初始化一下,然后我们再看一下他的doFilter方法,@Overridepublic 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) {delegateToUse = this.delegate;if (delegateToUse == null) {WebApplicationContext wac = findWebApplicationContext();if (wac == null) {throw new IllegalStateException("No WebApplicationContext found: " +"no ContextLoaderListener or DispatcherServlet registered?");}delegateToUse = initDelegate(wac);}this.delegate = delegateToUse;}}// Let the delegate perform the actual doFilter operation.invokeDelegate(delegateToUse, request, response, filterChain);}所以每个请求都会走到doFilter方法,我们主要看这个方法,invokeDelegate,/*** Actually invoke the delegate Filter with the given request and response.* @param delegate the delegate Filter* @param request the current HTTP request* @param response the current HTTP response* @param filterChain the current FilterChain* @throws ServletException if thrown by the Filter* @throws IOException if thrown by the Filter*/protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)throws ServletException, IOException {delegate.doFilter(request, response, filterChain);}也就是在执行doFilter里边,他又调用了一个委托的一个方法,我们进来看一下,从他的执行实现里边呢,可以看出来,他又调用了委托的doFilter方法,我们看看他所属的包,第一个所属Spring session包,第二个呢,所属spring web 4.0.3 release,那我们肯定是看spring session包的,那这个类的名字叫OncePerRequestFilter,也就是我每一次只filter一次,那所以这里会做一个判断,它是如何判断的呢,在这里是否是被filter的attribute,然后从里面拿了一个attribute,如果拿到的值不等于空,那就对他做一个判断,/*** 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);}}}而this.alreadyFilteredAttributeName呢,这个name就是使用这个类名,连上这个常量,而这个常量呢,private String alreadyFilteredAttributeName = getClass().getName().concat(ALREADY_FILTERED_SUFFIX);就是已经被过滤的,通过doFilter方法可以得知,首先它通过request里面的attribute,判断是否已经过滤,每次请求只做一次过滤逻辑,如果这个请求是第一次进入这个filter,并且执行现在的doFilter方法,那就会调用这个方法,而这个方法呢

doFilterInternal方法,之前我们说过Spring Session框架呢,会对现有的request和response,进行一个包装,@Overrideprotected 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和SessionRepositoryResponseWrapper这两个类,包装好的request和response,我们看一下这个类,看一下SessionRepositoryRequestWrapper的类图

首先它继承了HttpServletRequestWrapper,而这个是servlet原生提供的一个类,他继承他之后呢,他就拥有了可以包装request的一个功能,通过重写方法的一个方式,那response也是同理,这里面覆盖了很多方法,首先拿getSession来说@Overridepublic HttpSessionWrapper getSession() {return getSession(true);}他首先执行这个方法,这个方法就把getSession方法进行了一个重写,@Overridepublic HttpSessionWrapper getSession(boolean create) {HttpSessionWrapper currentSession = getCurrentSession();if (currentSession != null) {return currentSession;}String requestedSessionId = getRequestedSessionId();if (requestedSessionId != null&& getAttribute(INVALID_SESSION_ID_ATTR) == null) {S session = getSession(requestedSessionId);if (session != null) {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 requestif (SESSION_LOGGER.isDebugEnabled()) {SESSION_LOGGER.debug("No session found by id: Caching result for getSession(false) for this HttpServletRequest.");}setAttribute(INVALID_SESSION_ID_ATTR, "true");}}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.setLastAccessedTime(System.currentTimeMillis());currentSession = new HttpSessionWrapper(session, getServletContext());setCurrentSession(currentSession);return currentSession;}有兴趣的小伙伴可以看一下,setLastAccessedTime,这个值会存到redis当中,session又用HttpSessionWrapper进行了一个包装,SessionWrapper和requestWrapper,responseWrapper是同理的,然后把当前的session进行一个set,看一下set是如何做的private void setCurrentSession(HttpSessionWrapper currentSession) {if (currentSession == null) {removeAttribute(this.CURRENT_SESSION_ATTR);}else {setAttribute(this.CURRENT_SESSION_ATTR, currentSession);}}如果为空的话就remove,如果不为空就set上,把currentSession都放到这里,而KEY是一个常量,private final String CURRENT_SESSION_ATTR = HttpServletRequestWrapper.class.getName();reqeuestWrapper的一个类名,那我们还要看一个关键的类,HttpSessionStrategy,它是一个接口,那我们看一下这个接口有哪些方法,首先是获取sessionId/*** Obtains the requested session id from the provided* {@link javax.servlet.http.HttpServletRequest}. For example, the session id might* come from a cookie or a request header.** @param request the {@link javax.servlet.http.HttpServletRequest} to obtain the* session id from. Cannot be null.* @return the {@link javax.servlet.http.HttpServletRequest} to obtain the session id* from.*/String getRequestedSessionId(HttpServletRequest request);从request当中获取sessionId,因为参数是一个request,例如我们的sessionId就存到了cookie当中,那还有一种方式就是把sessionId放到请求的header当中,那咱们用的是cookie,还有一个onNewSession/*** This method is invoked when a new session is created and should inform a client* what the new session id is. For example, it might create a new cookie with the* session id in it or set an HTTP response header with the value of the new session* id.** Some implementations may wish to associate additional information to the* {@link Session} at this time. For example, they may wish to add the IP Address,* browser headers, the username, etc to the* {@link org.springframework.session.Session}.** @param session the {@link org.springframework.session.Session} that is being sent* to the client. Cannot be null.* @param request the {@link javax.servlet.http.HttpServletRequest} that create the* new {@link org.springframework.session.Session} Cannot be null.* @param response the {@link javax.servlet.http.HttpServletResponse} that is* associated with the {@link javax.servlet.http.HttpServletRequest} that created the* new {@link org.springframework.session.Session} Cannot be null.*/void onNewSession(Session session, HttpServletRequest request,HttpServletResponse response);这里面传了三个参数,request,response,这个方法是用到什么时候呢,是当新session被创建,并创建新的sessionId时,这个方法才会被调用,简单理解为new session的时候,才会被调用,/*** This method is invoked when a session is invalidated and should inform a client* that the session id is no longer valid. For example, it might remove a cookie with* the session id in it or set an HTTP response header with an empty value indicating* to the client to no longer submit that session id.** @param request the {@link javax.servlet.http.HttpServletRequest} that invalidated* the {@link org.springframework.session.Session} Cannot be null.* @param response the {@link javax.servlet.http.HttpServletResponse} that is* associated with the {@link javax.servlet.http.HttpServletRequest} that invalidated* the {@link org.springframework.session.Session} Cannot be null.*/void onInvalidateSession(HttpServletRequest request, HttpServletResponse response);当这个session失效的时候,才会被调用,我们看一下他的实现都有哪些

刚刚说的放到requestHeader里面呢,就是这种实现,而我们是使用的cookie这种实现方式,这个就是cookie session的一个策略,CookieHttpSessionStrategy,这里面的核心方法我们可以看一个,比如getRequestedSessionIdpublic String getRequestedSessionId(HttpServletRequest request) {Map<String, String> sessionIds = getSessionIds(request);String sessionAlias = getCurrentSessionAlias(request);return sessionIds.get(sessionAlias);}那这个方法首先调用了getSessionIds,从request当中获取,然后拼接成一个mappublic Map<String, String> getSessionIds(HttpServletRequest request) {List<String> cookieValues = this.cookieSerializer.readCookieValues(request);String sessionCookieValue = cookieValues.isEmpty() ? "": cookieValues.iterator().next();Map<String, String> result = new LinkedHashMap<String, String>();StringTokenizer tokens = new StringTokenizer(sessionCookieValue, " ");if (tokens.countTokens() == 1) {result.put(DEFAULT_ALIAS, tokens.nextToken());return result;}while (tokens.hasMoreTokens()) {String alias = tokens.nextToken();if (!tokens.hasMoreTokens()) {break;}String id = tokens.nextToken();result.put(alias, id);}return result;}这个方法从request中获取cookieValues,并把alias别名和id,维护成一个map,也就是result,然后对他进行一个返回,这个时候就调用这个方法getCurrentSessionAlias,获取当前session的别名,通过request拿到session的一个别名,然后通过刚刚得到一个map,组装成一个map,还有session的一个别名,来get一个string,而String就是sessionId的一个value,有兴趣的可以仔细阅读一下里面的源码,然后我们再回来SessionRepository接口,这个接口有几个方法呢,有4个,分别是创建session,删除,和save session,那save session就是持久化了,我们的实现方案是使用redis

RedisOperationsSessionRepository这个类,他有哪些常量呢,记得我们Spring session存的前缀,那就在这里/*** The prefix of the key for used for session attributes. The suffix is the name of* the session attribute. For example, if the session contained an attribute named* attributeName, then there would be an entry in the hash named* sessionAttr:attributeName that mapped to its value.*/static final String SESSION_ATTR_PREFIX = "sessionAttr:";还有这么一个keyPrefix/*** The prefix for every key used by Spring Session in Redis.*/private String keyPrefix = DEFAULT_SPRING_SESSION_REDIS_PREFIX;key的prefix,我们看一下这个常量是什么,/*** The default prefix for each key and channel in Redis used by Spring Session.*/static final String DEFAULT_SPRING_SESSION_REDIS_PREFIX = "spring:session:";所以就和我们之前看到的都对上了,那从名字就可以看出来,这里面会有大量的对redis的一个操作,下面还有获取session的一个实现,public RedisSession getSession(String id) {return getSession(id, false);}而这个实现又调用了这个方法/*** Gets the session.* @param id the session id* @param allowExpired if true, will also include expired sessions that have not been* deleted. If false, will ensure expired sessions are not returned.* @return the Redis session*/private RedisSession getSession(String id, boolean allowExpired) {Map<Object, Object> entries = getSessionBoundHashOperations(id).entries();if (entries.isEmpty()) {return null;}MapSession loaded = loadSession(id, entries);if (!allowExpired && loaded.isExpired()) {return null;}RedisSession result = new RedisSession(loaded);result.originalLastAccessTime = loaded.getLastAccessedTime();return result;}还有loadSessionprivate MapSession loadSession(String id, Map<Object, Object> entries) {MapSession loaded = new MapSession(id);for (Map.Entry<Object, Object> entry : entries.entrySet()) {String key = (String) entry.getKey();if (CREATION_TIME_ATTR.equals(key)) {loaded.setCreationTime((Long) entry.getValue());}else if (MAX_INACTIVE_ATTR.equals(key)) {loaded.setMaxInactiveIntervalInSeconds((Integer) entry.getValue());}else if (LAST_ACCESSED_ATTR.equals(key)) {loaded.setLastAccessedTime((Long) entry.getValue());}else if (key.startsWith(SESSION_ATTR_PREFIX)) {loaded.setAttribute(key.substring(SESSION_ATTR_PREFIX.length()),entry.getValue());}}return loaded;}还有DefaultCookieSerializer这么一个类,之前我们在注入cookie的时候有讲,所以这个类也是非常重要的,那通过Spring session的源码解析呢,我们把刚刚关键的几个类,都介绍了一遍

Spring Session源码解析相关推荐

  1. 【SpringBoot系列】 Spring中自定义Session管理,Spring Session源码解析

    系列文章:Spring Boot学习大纲,可以留言自己想了解的技术点 目录 系列文章:Spring Boot学习大纲,可以留言自己想了解的技术

  2. spring事务源码解析

    前言 在spring jdbcTemplate 事务,各种诡异,包你醍醐灌顶!最后遗留了一个问题:spring是怎么样保证事务一致性的? 当然,spring事务内容挺多的,如果都要讲的话要花很长时间, ...

  3. spring boot 源码解析23-actuate使用及EndPoint解析

    前言 spring boot 中有个很诱人的组件–actuator,可以对spring boot应用做监控,只需在pom文件中加入如下配置即可: <dependency><group ...

  4. Spring AOP源码解析-拦截器链的执行过程

    一.简介 在前面的两篇文章中,分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在得到了 bean 的代理对象,且通知也以合适的方式插在了目标方 ...

  5. Spring MVC源码解析——HandlerMapping(处理器映射器)

    Sping MVC 源码解析--HandlerMapping处理器映射器 1. 什么是HandlerMapping 2. HandlerMapping 2.1 HandlerMapping初始化 2. ...

  6. Spring AOP源码解析(一)——核心概念

    目录 Pointcut ClassFilter MethodMatcher Advisor IntroductionAdvisor PointcutAdvisor AbstractPointcutAd ...

  7. Spring @Import源码解析

    在Spring boot中常用到@Import,允许通过它引入 @Configuration 注解的类 (java config), 引入ImportSelector接口(这个比较重要, 因为要通过它 ...

  8. Spring AOP源码解析——AOP动态代理原理和实现方式

    2019独角兽企业重金招聘Python工程师标准>>> Spring介绍 Spring(http://spring.io/)是一个轻量级的Java 开发框架,同时也是轻量级的IoC和 ...

  9. Laravel核心解读--Session源码解析

    Session 模块源码解析 由于HTTP最初是一个匿名.无状态的请求/响应协议,服务器处理来自客户端的请求然后向客户端回送一条响应.现代Web应用程序为了给用户提供个性化的服务往往需要在请求中识别出 ...

最新文章

  1. mysql 中文搜索插件_支持中文的MySQL 5.1+ 全文检索分词插件
  2. 如何统计各个班级的相关数据(如班级排名、最高分等)呢?
  3. 别顾着学习工作,没了生活
  4. 如何给图片赋值_医学数据的变量类型及在SPSS中的赋值方法(医学统计前的重要步骤)——【杏花开医学统计】...
  5. 【鸿蒙 HarmonyOS】界面跳转 ( AbilitySlice 之间的界面跳转 | AbilitySlice 之间的值传递 )
  6. 延时求和波束形成的MATLAB仿真
  7. EOS账户系统(7)权限评估
  8. JVM--字节码执行引擎
  9. 11g下如何查询trace文件名
  10. acm 凹多边形面积_解析几何|面积的计算
  11. 值传递与引用传递区别,具体表现
  12. 求职必看!大厂面试中遇到了发散性问题..... ,怎么办?
  13. Atitit it软件领域职称评级规定,广博方向。 目录 1. 软件工程师资格证 1 1.1. 法规规范 十大标准,三级五晋制。 1 1.2. 组织架构 域职称评级委员会 2 1.3. 人员职责流程表
  14. linux go missing git command,go: missing Git command的解决办法
  15. 修改美化pycharm主题
  16. R语言绘制列线图nomogram分步骤从头到尾实战
  17. 车载一体机凯立德导航升级
  18. MSM8260,OMAP4430,TEGRA2,EXYNOS 4210详细分析
  19. 我爱赚钱吧:你也可以通过建网站赚钱的④
  20. vue-element-admin安装依赖失败问题

热门文章

  1. mini2440的时钟
  2. 通过C++ Interop把Windows窗体集成到MFC应用程序中
  3. 细述:nginx http内核模块提供的变量和解释
  4. python几乎无所不能 只有你不知道的,如何通过Python玩转小视频
  5. vim 粘贴代码格式
  6. 使用移动自适应布局+easy mock实现移动界面的简单实现
  7. oracle 动态注册和静态注册
  8. Everything you need to learn about engineering freelance
  9. java HashMap实现原理
  10. linux打补丁前如何备份,关于Linux下给文件打补丁