使用spring-session-jdbc的Filter后session对象在后面的CompositeFilter中为空

After using Filter of spring-session-jdbc session object is empty in later CompositeFilter

问题描述(好像是时间问题):

After using SpringSessionRepositoryFilter, session object is empty during the processing period of OtherFilter at the begining of every request

我试过的:

  • In the Controller and JSP after OtherFilter, session object is not empty and works fine
  • Without using springSessionRepositoryFilter, session object is notempty and works fine in OtherFilter

配置如下:

<bean class="org.springframework.web.filter.CompositeFilter" name="springChainFilter">
    <property name="filters">
        <list>
            <bean id="springSessionRepositoryFilter" class="org.springframework.session.web.http.SessionRepositoryFilter">
            </bean>

            <!--Other Later Filter -->
            <bean id="otherFilter" class="xxx.xxx.OtherFilter">
            </bean>
        </list>
    </property>
</bean>

<bean class="org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration"/>

<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <constructor-arg ref="dataSource"/>
</bean>

<bean id="cookieSerializer" class="org.springframework.session.web.http.DefaultCookieSerializer">
    <property name="cookieName" value="JSESSIONID" />
</bean>

OtherFilter 定义如下:

public class OtherFilter extends OncePerRequestFilter {


    @Autowired
    private SessionObj sessionObj;

    ......
}

会话对象定义如下:

@Component
@SessionScope
public class SessionObj implements Serializable {

    private static final long serialVersionUID = 1L;


    private String xxId;

    ......
}

环境版本信息:

  • spring-session-jdbc-2.1.5.RELEASE
  • wildfly-11.0.0.Final
  • Oracle Database 18c Express Edition Release 18.0.0.0.0

这个问题的原因

  • @Autowired SessionObj in OtherFilter
  • @Autowired SessionObj in Controller after OtherFilter

以上两种情况,DI源都是绑定到[requestAttributes]对象的request#getsession()。

org.springframework.web.context.request.ServletRequestAttributes

    protected final HttpSession getSession(boolean allowCreate) {
        if (isRequestActive()) {
            HttpSession session = this.request.getSession(allowCreate);
            this.session = session;
            return session;
        }
    }

在一个请求中有以下里程碑:

从a到e的序列

a. In RequestContextListner

  • 对象 [requestAttributes] 与一个实例绑定 HttpServletRequestImpl(没有来自数据库的会话信息)

    org.springframework.web.context.request.RequestContextListener

    public void requestInitialized(ServletRequestEvent requestEvent) {
        if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {
            throw new IllegalArgumentException(
                    "Request is not an HttpServletRequest: " + requestEvent.getServletRequest());
        }
        HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
        ServletRequestAttributes attributes = new ServletRequestAttributes(request);
        request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
        LocaleContextHolder.setLocale(request.getLocale());
        RequestContextHolder.setRequestAttributes(attributes);
    }
    

b. In SessionRepositoryFilter

  • 请求被 SessionRepositoryRequestWrapper 包裹(with session 来自 DB 的信息)
  • 但是,对象 [requestAttributes] 没有重新绑定 使用 SessionRepositoryRequestWrapper

    实例

    org.springframework.session.web.http.SessionRepositoryFilter

    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);
        SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
                wrappedRequest, response);
    
        try {
            filterChain.doFilter(wrappedRequest, wrappedResponse);
        }
    }
    

c. In OtherFilter

  • SessionObj 是通过 [requestAttributes] 注入的 HttpServletRequestImpl 的实例(没有来自数据库的会话信息),所以 它是空的

d. In FrameworkServlet

  • 对象 [requestAttributes] 与一个实例重新绑定 SessionRepositoryRequestWrapper(带有来自数据库的会话信息)这是 包裹在前面的b进程中

    org.springframework.web.servlet.FrameworkServlet

    protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
        initContextHolders(request, localeContext, requestAttributes);
        }
    
    private void initContextHolders(HttpServletRequest request,
            @Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {
    
        if (requestAttributes != null) {
            RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
        }
    
    }
    

e. In Controller after OtherFilter

  • SessionObj 是通过 [requestAttributes] 注入的 SessionRepositoryRequestWrapper 实例(会话信息来自 DB),所以它工作正常

这个问题的一个解决方案

Add a Session Retrieve Filter between springSessionRepositoryFilter and otherFilter

<bean class="org.springframework.web.filter.CompositeFilter" name="springChainFilter">
    <property name="filters">
        <list>
            <bean id="springSessionRepositoryFilter" class="org.springframework.session.web.http.SessionRepositoryFilter">
            </bean>
            <!--Session Retrieve Filter -->
            <bean id="sessionRetrieveFilter" class="xxx.xxx.SessionRetrieveFilter">
            <!--Other Later Filter -->
            <bean id="otherFilter" class="xxx.xxx.OtherFilter">
            </bean>
        </list>
    </property>
</bean>

In Session Retrieve Filter bind the [requestAttributes] with an instance of SessionRepositoryRequestWrapper (do thing like FrameworkSevlet do in future)

xxx.xxx.SessionRetrieveFilter

    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        RequestAttributes wrappedAttributes = new ServletRequestAttributes(request, response);
        RequestContextHolder.setRequestAttributes(wrappedAttributes);

        filterChain.doFilter(request, response);
    } 

这个问题的另一个解决方案

Add the requestContextFilter between springSessionRepositoryFilter and otherFilter

<bean class="org.springframework.web.filter.CompositeFilter" name="springChainFilter">
    <property name="filters">
        <list>
            <bean id="springSessionRepositoryFilter" class="org.springframework.session.web.http.SessionRepositoryFilter">
            </bean>
            <!--Request Context Filter -->
            <bean id="requestContextFilter" class="org.springframework.web.filter.RequestContextFilter" />
            <!--Other Later Filter -->
            <bean id="otherFilter" class="xxx.xxx.OtherFilter">
            </bean>
        </list>
    </property>
</bean>

RequestContextFilter中(由spring框架应用)

  • wrappedRequest 暴露给当前线程,通过两者 LocaleContextHolder 和 RequestContextHolder
  • [requestAttributes] 绑定了一个实例 SessionRepositoryRequestWrapper(像 FrameworkSevlet 那样做 未来)