springsession源码分析一之代理入口

时间:2021-04-08 09:23:26

环境准备

  1. 版本
JDK1.8
spring-session 1.2
spring-data-redis 1.7

入口DelegatingFilterProxy

1.uml图
springsession源码分析一之代理入口
2. org.springframework.web.filter.DelegatingFilterProxy 是session过滤器的代理入口。
3. DelegatingFilterProxy继承抽象类GenericFilterBean,GenericFilterBean在init方法中调用了抽象方法initFilterBean,让子类去初始化自己的。DelegatingFilterProxy实现的initFilterBean如下图所示

在抽象模板类GenericFilterBean中的初始化方法init()最后调用
@Override
    protected 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);
                }
            }
        }
    }

springsession源码分析一之代理入口
英文注释的大意:如果应用上下文ApplicationContext在代理Filter DelegatingFilterProxy 之后启动,我们不得不采取延迟初始化。也就是说,如果ApplicationContext在过滤器之后初始化,则在doFilter中重新获取上下文对象。

springsession源码分析一之代理入口

  • 获取实际的委托类,通过targetBeanName。Filter delegate = wac.getBean(getTargetBeanName(), Filter.class); 此处的targetName是springSessionRepositoryFilter
  • RedisHttpSessionConfiguration 注入相关的Bean,它的父类SpringHttpSessionConfiguration注入SessionRepositoryFilter(具体详见RedisHttpSessionConfiguration解析)
  • DelegatingFilterProxy doFilter的主要作用是如果委托Filter没初始化就初始化代理,然后直接调用委托的doFilter
    springsession源码分析一之代理入口
    springsession源码分析一之代理入口

委托代理类SessionRepositoryFilter

  1. uml图
    springsession源码分析一之代理入口

  2. 分析入口,从doFilter入手。但是SessionRepositoryFilter本身没有doFilter方法,doFilter方法在其父类OncePerRequestFilter中(思考:OncePerRequestFilter 一次请求,spring写这个过滤器的用意是什么??)。

  3. 思考解析:OncePerRequestFilter是用来保证一次完整的拦截链中,同一个类只会被调用一次。 因为spring无法保证同一个Filter类只有一个实例。有可能一个Filter既有可能在web.xml里配置由容器初始化了,还有可能被作为spring的依赖引入进了DelegatingFilterProxy。这样在一次filter chain中就会存在同一个Filter的多个实例。
  4. OncePerRequestFilter的doFilterInternal 是抽象方法,让子类实现。所以我们直接查看SessionRepositoryFilter的doFilterInternal方法即可。
    SessionRepositoryRequestWrapper对request对象进行增强(包装),使用的是装饰设计模式,默认是tomcat的实现类org.apache.catalina.connector.RequestFacade
    SessionRepositoryResponseWrapper对response对象进行增强,默认是tomcat的实现类org.apache.catalina.connector.ResponseFacade@1a719b4
    SessionRepositoryRequestWrapper和SessionRepositoryResponseWrapper都是SessionRepositoryFilter的内部类

        此方法在抽象父类OncePerRequestFilter 的doFilter中调用
        @Override
            protected void doFilterInternal(HttpServletRequest request,
                    HttpServletResponse response, FilterChain filterChain)
                            throws ServletException, IOException {
                request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
                //装饰模式,对request和response进行增强
                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();
                }
            }
  5. 存储

    HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 \ 
        maxInactiveInterval 1800 \
        lastAccessedTime 1404360000000 \
        sessionAttr:attrName someAttrValue \
        sessionAttr2:attrName someAttrValue2
    EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
    APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
    EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
    SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe
    EXPIRE spring:session:expirations1439245080000 2100
    每一个Session使用redis的Hash存储(HMSET)
        33fdd1b6-b496-4b33-9f7d-df96679d32fe 是SessionId
        creationTime 1404360000000  创建时间
        maxInactiveInterval 1800    过期时间
        lastAccessedTime 1404360000000 上次访问时间


    Expiration is not tracked directly on the session key itself since this would mean the session data would no longer be available. Instead a special session expires key is used. In our example the expires key is:
    过期没有直接在Sessinon key本身进行跟踪,因为这意味着Session 数据不再可以利用。而是使用一个特殊的过期Session key ,从下面可以看到是定义一个String类型的key,这个key的值为空串("")
    APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
    EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800