前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Session关键类源码分析

Spring Session关键类源码分析

作者头像
JavaEdge
发布2018-04-28 16:32:29
1.1K0
发布2018-04-28 16:32:29
举报
文章被收录于专栏:JavaEdgeJavaEdge

要想使用spring session,还需要创建名为springSessionRepositoryFilter的SessionRepositoryFilter类。该类实现了Sevlet Filter接口,当请求穿越sevlet filter链时应该首先经过springSessionRepositoryFilter,这样在后面获取session的时候,得到的将是spring session。为了springSessonRepositoryFilter作为filter链中的第一个,spring session提供了AbstractHttpSessionApplicationInitializer类, 它实现了WebApplicationInitializer类,在onStartup方法中将springSessionRepositoryFilter加入到其他fitler链前面。

代码语言:javascript
复制
public abstract class AbstractHttpSessionApplicationInitializer
        implements WebApplicationInitializer {
    /**
     * The default name for Spring Session's repository filter.
     */
    public static final String DEFAULT_FILTER_NAME = "springSessionRepositoryFilter";
    public void onStartup(ServletContext servletContext) throws ServletException {
            ......
        insertSessionRepositoryFilter(servletContext);
        afterSessionRepositoryFilter(servletContext);
    }
    /**
     * 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);
    }
}

或者也可以在web.xml里面将springSessionRepositoryFilter加入到filter配置的第一个 该filter最终会把请求代理给具体的一个filter,通过入参的常量可看出它是委派给springSessionRepositoryFilter这样一个具体的filter(由spring容器管理)

DelegatingFilterProxy.png

查看其父类

代码语言:javascript
复制
public abstract class GenericFilterBean implements
        Filter, BeanNameAware, EnvironmentAware, ServletContextAware, InitializingBean, DisposableBean {

    /** Logger available to subclasses */
    protected final Log logger = LogFactory.getLog(getClass());

    /**
     * Set of required properties (Strings) that must be supplied as
     * config parameters to this filter.
     */
    private final Set<String> requiredProperties = new HashSet<String>();

    private FilterConfig filterConfig;

    private String beanName;

    private Environment environment = new StandardServletEnvironment();

    private ServletContext servletContext;

    /**
     * Calls the {@code initFilterBean()} method that might
     * contain custom initialization of a subclass.
     * <p>Only relevant in case of initialization as bean, where the
     * standard {@code init(FilterConfig)} method won't be called.
     * @see #initFilterBean()
     * @see #init(javax.servlet.FilterConfig)
     */
    @Override
    public void afterPropertiesSet() throws ServletException {
        initFilterBean();
    }


    /**
     * Subclasses can invoke this method to specify that this property
     * (which must match a JavaBean property they expose) is mandatory,
     * and must be supplied as a config parameter. This should be called
     * from the constructor of a subclass.
     * <p>This method is only relevant in case of traditional initialization
     * driven by a FilterConfig instance.
     * @param property name of the required property
     */
    protected final void addRequiredProperty(String property) {
        this.requiredProperties.add(property);
    }

    /**
     * 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
     */
    @Override
    public 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.
        try {
            PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
            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.
        //初始化filter bean
        initFilterBean();

        if (logger.isDebugEnabled()) {
            logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
        }
    }

    /**
     * Initialize the BeanWrapper for this GenericFilterBean,
     * possibly with custom editors.
     * <p>This default implementation is empty.
     * @param bw the BeanWrapper to initialize
     * @throws BeansException if thrown by BeanWrapper methods
     * @see org.springframework.beans.BeanWrapper#registerCustomEditor
     */
    protected void initBeanWrapper(BeanWrapper bw) throws BeansException {
    }


    /**
     * Make the FilterConfig of this filter available, if any.
     * Analogous to GenericServlet's {@code getServletConfig()}.
     * <p>Public to resemble the {@code getFilterConfig()} method
     * of the Servlet Filter version that shipped with WebLogic 6.1.
     * @return the FilterConfig instance, or {@code null} if none available
     * @see javax.servlet.GenericServlet#getServletConfig()
     */
    public final FilterConfig getFilterConfig() {
        return this.filterConfig;
    }

    /**
     * Make the name of this filter available to subclasses.
     * Analogous to GenericServlet's {@code getServletName()}.
     * <p>Takes the FilterConfig's filter name by default.
     * If initialized as bean in a Spring application context,
     * it falls back to the bean name as defined in the bean factory.
     * @return the filter name, or {@code null} if none available
     * @see javax.servlet.GenericServlet#getServletName()
     * @see javax.servlet.FilterConfig#getFilterName()
     * @see #setBeanName
     */
    protected final String getFilterName() {
        return (this.filterConfig != null ? this.filterConfig.getFilterName() : this.beanName);
    }

    /**
     * Make the ServletContext of this filter available to subclasses.
     * Analogous to GenericServlet's {@code getServletContext()}.
     * <p>Takes the FilterConfig's ServletContext by default.
     * If initialized as bean in a Spring application context,
     * it falls back to the ServletContext that the bean factory runs in.
     * @return the ServletContext instance, or {@code null} if none available
     * @see javax.servlet.GenericServlet#getServletContext()
     * @see javax.servlet.FilterConfig#getServletContext()
     * @see #setServletContext
     */
    protected final ServletContext getServletContext() {
        return (this.filterConfig != null ? this.filterConfig.getServletContext() : this.servletContext);
    }


    /**
     * Subclasses may override this to perform custom initialization.
     * All bean properties of this filter will have been set before this
     * method is invoked.
     */
    protected void initFilterBean() throws ServletException {
    }

    /**
     * Subclasses may override this to perform custom filter shutdown.
     * <p>Note: This method will be called from standard filter destruction
     * as well as filter bean destruction in a Spring application context.
     * <p>This default implementation is empty.
     */
    @Override
    public void destroy() {
    }


    /**
     * PropertyValues implementation created from FilterConfig init parameters.
     */
    @SuppressWarnings("serial")
    private static class FilterConfigPropertyValues extends MutablePropertyValues {

        /**
         * Create new FilterConfigPropertyValues.
         */
        public FilterConfigPropertyValues(FilterConfig config, Set<String> requiredProperties)
            throws ServletException {

            Set<String> missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ?
                    new HashSet<String>(requiredProperties) : null;

            Enumeration<?> en = config.getInitParameterNames();
            while (en.hasMoreElements()) {
                String property = (String) en.nextElement();
                Object value = config.getInitParameter(property);
                addPropertyValue(new PropertyValue(property, value));
                if (missingProps != null) {
                    missingProps.remove(property);
                }
            }

            // Fail if we are still missing properties.
            if (missingProps != null && missingProps.size() > 0) {
                throw new ServletException(
                    "Initialization from FilterConfig for filter '" + config.getFilterName() +
                    "' failed; the following required properties were missing: " +
                    StringUtils.collectionToDelimitedString(missingProps, ", "));
            }
        }
    }

}

查看其真正实现方法的子类

代码语言:javascript
复制
public class DelegatingFilterProxy extends GenericFilterBean {

    private String contextAttribute;

    private WebApplicationContext webApplicationContext;

    private String targetBeanName;

    private boolean targetFilterLifecycle = false;

    private volatile Filter delegate;

    private final Object delegateMonitor = new Object();

    public DelegatingFilterProxy() {
    }

    public DelegatingFilterProxy(Filter delegate) {
        Assert.notNull(delegate, "delegate Filter object must not be null");
        this.delegate = delegate;
    }

    public DelegatingFilterProxy(String targetBeanName) {
        this(targetBeanName, null);
    }

    public DelegatingFilterProxy(String targetBeanName, WebApplicationContext wac) {
        Assert.hasText(targetBeanName, "target Filter bean name must not be null or empty");
        this.setTargetBeanName(targetBeanName);
        this.webApplicationContext = wac;
        if (wac != null) {
            this.setEnvironment(wac.getEnvironment());
        }
    }
    
    protected boolean isTargetFilterLifecycle() {
        return this.targetFilterLifecycle;
    }

    @Override
    protected void initFilterBean() throws ServletException {
        //同步块,防止spring容器启动时委托的这些filter保证它们的执行顺序
        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);
                }
            }
        }
    }

    @Override
    public 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) {
                if (this.delegate == null) {
                    WebApplicationContext wac = findWebApplicationContext();
                    if (wac == null) {
                        throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
                    }
                    this.delegate = initDelegate(wac);
                }
                delegateToUse = this.delegate;
            }
        }

        // Let the delegate perform the actual doFilter operation.
        invokeDelegate(delegateToUse, request, response, filterChain);
    }

    @Override
    public void destroy() {
        Filter delegateToUse = this.delegate;
        if (delegateToUse != null) {
            destroyDelegate(delegateToUse);
        }
    }

    protected WebApplicationContext findWebApplicationContext() {
        if (this.webApplicationContext != null) {
            // the user has injected a context at construction time -> use it
            if (this.webApplicationContext instanceof ConfigurableApplicationContext) {
                if (!((ConfigurableApplicationContext)this.webApplicationContext).isActive()) {
                    // the context has not yet been refreshed -> do so before returning it
                    ((ConfigurableApplicationContext)this.webApplicationContext).refresh();
                }
            }
            return this.webApplicationContext;
        }
        String attrName = getContextAttribute();
        if (attrName != null) {
            return WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
        }
        else {
            return WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        }
    }

    protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
        Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
        if (isTargetFilterLifecycle()) {
            delegate.init(getFilterConfig());
        }
        return delegate;
    }

    /**
     * Actually invoke the delegate Filter with the given request and response.
     * 调用了委托的doFilter方法
     */
    protected void invokeDelegate(
            Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        delegate.doFilter(request, response, filterChain);
    }

    /**
     * Destroy the Filter delegate.
     * Default implementation simply calls {@code Filter.destroy} on it.
     */
    protected void destroyDelegate(Filter delegate) {
        if (isTargetFilterLifecycle()) {
            delegate.destroy();
        }
    }
}

查看doFilter真正实现类(session包下) 每次只filter一次

代码语言:javascript
复制
abstract class OncePerRequestFilter implements Filter {
    /**
     * Suffix that gets appended to the filter name for the "already filtered" request
     * attribute.
     */
    public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";

    private String alreadyFilteredAttributeName = getClass().getName()
            .concat(ALREADY_FILTERED_SUFFIX);

    /**
     * This {@code doFilter} implementation stores a request attribute for
     * "already filtered", proceeding without filtering again if the attribute is already
     * there.
     */
    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;
        //判断是否已经被Filter
        boolean hasAlreadyFilteredAttribute = request
                .getAttribute(this.alreadyFilteredAttributeName) != null;

        if (hasAlreadyFilteredAttribute) {

            // Proceed without invoking this filter...
            filterChain.doFilter(request, response);
        }
        else {
            
            // Do invoke this filter...
            //确实调用此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);
            }
        }
    }

    /**
     * Same contract as for {@code doFilter}, but guaranteed to be just invoked once per
     * request within a single request thread.
     * <p>
     * Provides HttpServletRequest and HttpServletResponse arguments instead of the
     * default ServletRequest and ServletResponse ones.
     */
     //唯一实现子类:SessionRepositoryFilter!!!
    protected abstract void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain filterChain)
                    throws ServletException, IOException;

    public void init(FilterConfig config) {
    }

    public void destroy() {
    }
}

SessionRepositoryFilter.png

代码语言:javascript
复制
@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends ExpiringSession>
        extends OncePerRequestFilter {
    private static final String SESSION_LOGGER_NAME = SessionRepositoryFilter.class
            .getName().concat(".SESSION_LOGGER");

    private static final Log SESSION_LOGGER = LogFactory.getLog(SESSION_LOGGER_NAME);

    /**
     * The session repository request attribute name.
     */
    public static final String SESSION_REPOSITORY_ATTR = SessionRepository.class
            .getName();

    /**
     * Invalid session id (not backed by the session repository) request attribute name.
     */
    public static final String INVALID_SESSION_ID_ATTR = SESSION_REPOSITORY_ATTR
            + ".invalidSessionId";

    /**
     * The default filter order.
     */
    public static final int DEFAULT_ORDER = Integer.MIN_VALUE + 50;

    private final SessionRepository<S> sessionRepository;

    private ServletContext servletContext;

    private MultiHttpSessionStrategy httpSessionStrategy = new CookieHttpSessionStrategy();

    /**
     * Creates a new instance.
     */
    public SessionRepositoryFilter(SessionRepository<S> sessionRepository) {
        if (sessionRepository == null) {
            throw new IllegalArgumentException("sessionRepository cannot be null");
        }
        this.sessionRepository = sessionRepository;
    }

    /**
     * Sets the {@link HttpSessionStrategy} to be used.
     */
    public void setHttpSessionStrategy(HttpSessionStrategy httpSessionStrategy) {
        if (httpSessionStrategy == null) {
            throw new IllegalArgumentException("httpSessionStrategy cannot be null");
        }
        this.httpSessionStrategy = new MultiHttpSessionStrategyAdapter(
                httpSessionStrategy);
    }

    /**
     * Sets the {@link MultiHttpSessionStrategy} to be used.
     */
    public void setHttpSessionStrategy(MultiHttpSessionStrategy httpSessionStrategy) {
        if (httpSessionStrategy == null) {
            throw new IllegalArgumentException("httpSessionStrategy cannot be null");
        }
        this.httpSessionStrategy = httpSessionStrategy;
    }

    @Override
    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, 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();
        }
    }

    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    /**
     * Allows ensuring that the session is saved if the response is committed.
     * 对现有Request的一个包装类
     */
    private final class SessionRepositoryResponseWrapper
            extends OnCommittedResponseWrapper {

        private final SessionRepositoryRequestWrapper request;

        SessionRepositoryResponseWrapper(SessionRepositoryRequestWrapper request,
                HttpServletResponse response) {
            super(response);
            if (request == null) {
                throw new IllegalArgumentException("request cannot be null");
            }
            this.request = request;
        }

        @Override
        protected void onResponseCommitted() {
            this.request.commitSession();
        }
    }

    private final class SessionRepositoryRequestWrapper
            extends HttpServletRequestWrapper {
        private final String CURRENT_SESSION_ATTR = HttpServletRequestWrapper.class
                .getName();
        private Boolean requestedSessionIdValid;
        private boolean requestedSessionInvalidated;
        private final HttpServletResponse response;
        private final ServletContext servletContext;

        private SessionRepositoryRequestWrapper(HttpServletRequest request,
                HttpServletResponse response, ServletContext servletContext) {
            super(request);
            this.response = response;
            this.servletContext = servletContext;
        }

        /**
         * Uses the HttpSessionStrategy to write the session id to the response and
         * persist the Session.
         */
        private void commitSession() {
            HttpSessionWrapper wrappedSession = getCurrentSession();
            if (wrappedSession == null) {
                if (isInvalidateClientSession()) {
                    SessionRepositoryFilter.this.httpSessionStrategy
                            .onInvalidateSession(this, this.response);
                }
            }
            else {
                S session = wrappedSession.getSession();
                SessionRepositoryFilter.this.sessionRepository.save(session);
                if (!isRequestedSessionIdValid()
                        || !session.getId().equals(getRequestedSessionId())) {
                    SessionRepositoryFilter.this.httpSessionStrategy.onNewSession(session,
                            this, this.response);
                }
            }
        }

        @SuppressWarnings("unchecked")
        private HttpSessionWrapper getCurrentSession() {
            return (HttpSessionWrapper) getAttribute(this.CURRENT_SESSION_ATTR);
        }

        private void setCurrentSession(HttpSessionWrapper currentSession) {
            if (currentSession == null) {
                removeAttribute(this.CURRENT_SESSION_ATTR);
            }
            else {
                setAttribute(this.CURRENT_SESSION_ATTR, currentSession);
            }
        }

        @SuppressWarnings("unused")
        public String changeSessionId() {
            HttpSession session = getSession(false);

            if (session == null) {
                throw new IllegalStateException(
                        "Cannot change session ID. There is no session associated with this request.");
            }

            // eagerly get session attributes in case implementation lazily loads them
            Map<String, Object> attrs = new HashMap<String, Object>();
            Enumeration<String> iAttrNames = session.getAttributeNames();
            while (iAttrNames.hasMoreElements()) {
                String attrName = iAttrNames.nextElement();
                Object value = session.getAttribute(attrName);

                attrs.put(attrName, value);
            }

            SessionRepositoryFilter.this.sessionRepository.delete(session.getId());
            HttpSessionWrapper original = getCurrentSession();
            setCurrentSession(null);

            HttpSessionWrapper newSession = getSession();
            original.setSession(newSession.getSession());

            newSession.setMaxInactiveInterval(session.getMaxInactiveInterval());
            for (Map.Entry<String, Object> attr : attrs.entrySet()) {
                String attrName = attr.getKey();
                Object attrValue = attr.getValue();
                newSession.setAttribute(attrName, attrValue);
            }
            return newSession.getId();
        }

        @Override
        public boolean isRequestedSessionIdValid() {
            if (this.requestedSessionIdValid == null) {
                String sessionId = getRequestedSessionId();
                S session = sessionId == null ? null : getSession(sessionId);
                return isRequestedSessionIdValid(session);
            }

            return this.requestedSessionIdValid;
        }

        private boolean isRequestedSessionIdValid(S session) {
            if (this.requestedSessionIdValid == null) {
                this.requestedSessionIdValid = session != null;
            }
            return this.requestedSessionIdValid;
        }

        private boolean isInvalidateClientSession() {
            return getCurrentSession() == null && this.requestedSessionInvalidated;
        }

        private S getSession(String sessionId) {
            S session = SessionRepositoryFilter.this.sessionRepository
                    .getSession(sessionId);
            if (session == null) {
                return null;
            }
            session.setLastAccessedTime(System.currentTimeMillis());
            return session;
        }

        //重写的getSession方法
        @Override
        public 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 request
                    if (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();
            //会存到redis中
            session.setLastAccessedTime(System.currentTimeMillis());
            //对session也进行了包装(和request的包装同理)
            currentSession = new HttpSessionWrapper(session, getServletContext());
            setCurrentSession(currentSession);
            return currentSession;
        }

        @Override
        public ServletContext getServletContext() {
            if (this.servletContext != null) {
                return this.servletContext;
            }
            // Servlet 3.0+
            return super.getServletContext();
        }

        @Override
        public HttpSessionWrapper getSession() {
            return getSession(true);
        }

        @Override
        public String getRequestedSessionId() {
            return SessionRepositoryFilter.this.httpSessionStrategy
                    .getRequestedSessionId(this);
        }

        /**
         * Allows creating an HttpSession from a Session instance.
         */
        private final class HttpSessionWrapper extends ExpiringSessionHttpSession<S> {

            HttpSessionWrapper(S session, ServletContext servletContext) {
                super(session, servletContext);
            }

            @Override
            public void invalidate() {
                super.invalidate();
                SessionRepositoryRequestWrapper.this.requestedSessionInvalidated = true;
                setCurrentSession(null);
                SessionRepositoryFilter.this.sessionRepository.delete(getId());
            }
        }
    }

    /**
     * A delegating implementation of {@link MultiHttpSessionStrategy}.
     */
    static class MultiHttpSessionStrategyAdapter implements MultiHttpSessionStrategy {
        private HttpSessionStrategy delegate;

        /**
         * Create a new {@link MultiHttpSessionStrategyAdapter} instance.
         * @param delegate the delegate HTTP session strategy
         */
        MultiHttpSessionStrategyAdapter(HttpSessionStrategy delegate) {
            this.delegate = delegate;
        }

        public String getRequestedSessionId(HttpServletRequest request) {
            return this.delegate.getRequestedSessionId(request);
        }

        public void onNewSession(Session session, HttpServletRequest request,
                HttpServletResponse response) {
            this.delegate.onNewSession(session, request, response);
        }

        public void onInvalidateSession(HttpServletRequest request,
                HttpServletResponse response) {
            this.delegate.onInvalidateSession(request, response);
        }

        public HttpServletRequest wrapRequest(HttpServletRequest request,
                HttpServletResponse response) {
            return request;
        }

        public HttpServletResponse wrapResponse(HttpServletRequest request,
                HttpServletResponse response) {
            return response;
        }
    }
}

对现有Request的一个包装类

SessionRepositoryRequestWrapper.png

下面看另一个重要的类

代码语言:javascript
复制
/**
 * A strategy for mapping HTTP request and responses to a {@link Session}.
 */
public interface HttpSessionStrategy {

    /**
     * 从提供的HttpServletRequest获得所请求的会话ID。 例如,会话ID可能
     * 来自cookie或请求标头。
     */
    String getRequestedSessionId(HttpServletRequest request);

    /**
     * 在创建新会话时调用此方法,并通知客户端
     * 新的会话ID是什么。 例如,它可能会创建一个新的cookie
     * 会话ID或使用新会话的值设置HTTP响应头ID
     */
    void onNewSession(Session session, HttpServletRequest request,
            HttpServletResponse response);

    /**
     * 当会话失效并且应通知客户端时调用此方法
     * 该会话ID不再有效。 例如,它可能会删除一个cookie
     * 其中的会话标识或者设置一个空值指示的HTTP响应头
     * 到客户端不再提交该会话ID。
     */
    void onInvalidateSession(HttpServletRequest request, HttpServletResponse response);
}

来看一下它的一个实现类(由于我的项目使用的Cookie实现方式)

代码语言:javascript
复制
public final class CookieHttpSessionStrategy
        implements MultiHttpSessionStrategy, HttpSessionManager {
    private static final String SESSION_IDS_WRITTEN_ATTR = CookieHttpSessionStrategy.class
            .getName().concat(".SESSIONS_WRITTEN_ATTR");

    static final String DEFAULT_ALIAS = "0";

    static final String DEFAULT_SESSION_ALIAS_PARAM_NAME = "_s";

    private static final Pattern ALIAS_PATTERN = Pattern.compile("^[\\w-]{1,50}$");

    private String sessionParam = DEFAULT_SESSION_ALIAS_PARAM_NAME;

    private CookieSerializer cookieSerializer = new DefaultCookieSerializer();

    public String getRequestedSessionId(HttpServletRequest request) {
        
        Map<String, String> sessionIds = getSessionIds(request);
        //拿到别名
        String sessionAlias = getCurrentSessionAlias(request);
        //通过别名获取sessionId
        return sessionIds.get(sessionAlias);
    }

    public String getCurrentSessionAlias(HttpServletRequest request) {
        if (this.sessionParam == null) {
            return DEFAULT_ALIAS;
        }
        String u = request.getParameter(this.sessionParam);
        if (u == null) {
            return DEFAULT_ALIAS;
        }
        if (!ALIAS_PATTERN.matcher(u).matches()) {
            return DEFAULT_ALIAS;
        }
        return u;
    }

    public String getNewSessionAlias(HttpServletRequest request) {
        Set<String> sessionAliases = getSessionIds(request).keySet();
        if (sessionAliases.isEmpty()) {
            return DEFAULT_ALIAS;
        }
        long lastAlias = Long.decode(DEFAULT_ALIAS);
        for (String alias : sessionAliases) {
            long selectedAlias = safeParse(alias);
            if (selectedAlias > lastAlias) {
                lastAlias = selectedAlias;
            }
        }
        return Long.toHexString(lastAlias + 1);
    }

    private long safeParse(String hex) {
        try {
            return Long.decode("0x" + hex);
        }
        catch (NumberFormatException notNumber) {
            return 0;
        }
    }

    public void onNewSession(Session session, HttpServletRequest request,
            HttpServletResponse response) {
        Set<String> sessionIdsWritten = getSessionIdsWritten(request);
        if (sessionIdsWritten.contains(session.getId())) {
            return;
        }
        sessionIdsWritten.add(session.getId());

        Map<String, String> sessionIds = getSessionIds(request);
        String sessionAlias = getCurrentSessionAlias(request);
        sessionIds.put(sessionAlias, session.getId());

        String cookieValue = createSessionCookieValue(sessionIds);
        this.cookieSerializer
                .writeCookieValue(new CookieValue(request, response, cookieValue));
    }

    @SuppressWarnings("unchecked")
    private Set<String> getSessionIdsWritten(HttpServletRequest request) {
        Set<String> sessionsWritten = (Set<String>) request
                .getAttribute(SESSION_IDS_WRITTEN_ATTR);
        if (sessionsWritten == null) {
            sessionsWritten = new HashSet<String>();
            request.setAttribute(SESSION_IDS_WRITTEN_ATTR, sessionsWritten);
        }
        return sessionsWritten;
    }

    private String createSessionCookieValue(Map<String, String> sessionIds) {
        if (sessionIds.isEmpty()) {
            return "";
        }
        if (sessionIds.size() == 1 && sessionIds.keySet().contains(DEFAULT_ALIAS)) {
            return sessionIds.values().iterator().next();
        }

        StringBuffer buffer = new StringBuffer();
        for (Map.Entry<String, String> entry : sessionIds.entrySet()) {
            String alias = entry.getKey();
            String id = entry.getValue();

            buffer.append(alias);
            buffer.append(" ");
            buffer.append(id);
            buffer.append(" ");
        }
        buffer.deleteCharAt(buffer.length() - 1);
        return buffer.toString();
    }

    public void onInvalidateSession(HttpServletRequest request,
            HttpServletResponse response) {
        Map<String, String> sessionIds = getSessionIds(request);
        String requestedAlias = getCurrentSessionAlias(request);
        sessionIds.remove(requestedAlias);

        String cookieValue = createSessionCookieValue(sessionIds);
        this.cookieSerializer
                .writeCookieValue(new CookieValue(request, response, cookieValue));
    }


    public 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();
            //将别名与sessionId维护到一个map
            result.put(alias, id);
        }
        return result;
    }

    public HttpServletRequest wrapRequest(HttpServletRequest request,
            HttpServletResponse response) {
        request.setAttribute(HttpSessionManager.class.getName(), this);
        return request;
    }

    public HttpServletResponse wrapResponse(HttpServletRequest request,
            HttpServletResponse response) {
        return new MultiSessionHttpServletResponse(response, request);
    }

    public String encodeURL(String url, String sessionAlias) {
        String encodedSessionAlias = urlEncode(sessionAlias);
        int queryStart = url.indexOf("?");
        boolean isDefaultAlias = DEFAULT_ALIAS.equals(encodedSessionAlias);
        if (queryStart < 0) {
            return isDefaultAlias ? url
                    : url + "?" + this.sessionParam + "=" + encodedSessionAlias;
        }
        String path = url.substring(0, queryStart);
        String query = url.substring(queryStart + 1, url.length());
        String replacement = isDefaultAlias ? "" : "$1" + encodedSessionAlias;
        query = query.replaceFirst("((^|&)" + this.sessionParam + "=)([^&]+)?",
                replacement);
        if (!isDefaultAlias && url.endsWith(query)) {
            // no existing alias
            if (!(query.endsWith("&") || query.length() == 0)) {
                query += "&";
            }
            query += this.sessionParam + "=" + encodedSessionAlias;
        }

        return path + "?" + query;
    }

    private String urlEncode(String value) {
        try {
            return URLEncoder.encode(value, "UTF-8");
        }
        catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * A {@link CookieHttpSessionStrategy} aware {@link HttpServletResponseWrapper}.
     */
    class MultiSessionHttpServletResponse extends HttpServletResponseWrapper {
        private final HttpServletRequest request;

        MultiSessionHttpServletResponse(HttpServletResponse response,
                HttpServletRequest request) {
            super(response);
            this.request = request;
        }

        @Override
        public String encodeRedirectURL(String url) {
            url = super.encodeRedirectURL(url);
            return CookieHttpSessionStrategy.this.encodeURL(url,
                    getCurrentSessionAlias(this.request));
        }

        @Override
        public String encodeURL(String url) {
            url = super.encodeURL(url);

            String alias = getCurrentSessionAlias(this.request);
            return CookieHttpSessionStrategy.this.encodeURL(url, alias);
        }
    }

}

再看一个

代码语言:javascript
复制
/**
 * A repository interface for managing {@link Session} instances.
 */
public interface SessionRepository<S extends Session> {

    /**
     * Creates a new {@link Session} that is capable of being persisted by this
     * {@link SessionRepository}.
     *
     * <p>
     * This allows optimizations and customizations in how the {@link Session} is
     * persisted. For example, the implementation returned might keep track of the changes
     * ensuring that only the delta needs to be persisted on a save.
     * </p>
     *
     * @return a new {@link Session} that is capable of being persisted by this
     * {@link SessionRepository}
     */
    S createSession();

    /**
     * Ensures the {@link Session} created by
     * {@link org.springframework.session.SessionRepository#createSession()} is saved.
     *
     * <p>
     * Some implementations may choose to save as the {@link Session} is updated by
     * returning a {@link Session} that immediately persists any changes. In this case,
     * this method may not actually do anything.
     * </p>
     *
     * @param session the {@link Session} to save
     */
    void save(S session);

    /**
     * Gets the {@link Session} by the {@link Session#getId()} or null if no
     * {@link Session} is found.
     *
     * @param id the {@link org.springframework.session.Session#getId()} to lookup
     * @return the {@link Session} by the {@link Session#getId()} or null if no
     * {@link Session} is found.
     */
    S getSession(String id);

    /**
     * Deletes the {@link Session} with the given {@link Session#getId()} or does nothing
     * if the {@link Session} is not found.
     * @param id the {@link org.springframework.session.Session#getId()} to delete
     */
    void delete(String id);
}

因为使用redis.看这个实现类

代码语言:javascript
复制
public class RedisOperationsSessionRepository implements
        FindByIndexNameSessionRepository<RedisOperationsSessionRepository.RedisSession>,
        MessageListener {
    private static final Log logger = LogFactory
            .getLog(RedisOperationsSessionRepository.class);

    private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";

    static PrincipalNameResolver PRINCIPAL_NAME_RESOLVER = new PrincipalNameResolver();

    /**
     * Spring Session使用Redis中每个键和通道的默认前缀。
     */
    static final String DEFAULT_SPRING_SESSION_REDIS_PREFIX = "spring:session:";

    /**
     * The key in the Hash representing
     * {@link org.springframework.session.ExpiringSession#getCreationTime()}.
     */
    static final String CREATION_TIME_ATTR = "creationTime";

    /**
     * The key in the Hash representing
     * {@link org.springframework.session.ExpiringSession#getMaxInactiveIntervalInSeconds()}
     * .
     */
    static final String MAX_INACTIVE_ATTR = "maxInactiveInterval";

    /**
     * The key in the Hash representing
     * {@link org.springframework.session.ExpiringSession#getLastAccessedTime()}.
     */
    static final String LAST_ACCESSED_ATTR = "lastAccessedTime";

    /**
     * 用于会话属性的密钥的前缀。 后缀是名字
     * 会话属性。 例如,如果会话包含名为的属性attributeName,那么将会有一个名为hash的条目
     * sessionAttr:属性名映射到它的值。
     */
    static final String SESSION_ATTR_PREFIX = "sessionAttr:";

    /**
     * Redis中Spring会话使用的每个密钥的前缀
     */
    private String keyPrefix = DEFAULT_SPRING_SESSION_REDIS_PREFIX;

    private final RedisOperations<Object, Object> sessionRedisOperations;

    private final RedisSessionExpirationPolicy expirationPolicy;

    private ApplicationEventPublisher eventPublisher = new ApplicationEventPublisher() {
        public void publishEvent(ApplicationEvent event) {
        }

        public void publishEvent(Object event) {
        }
    };

    /**
     * If non-null, this value is used to override the default value for
     * {@link RedisSession#setMaxInactiveIntervalInSeconds(int)}.
     */
    private Integer defaultMaxInactiveInterval;

    private RedisSerializer<Object> defaultSerializer = new JdkSerializationRedisSerializer();

    private RedisFlushMode redisFlushMode = RedisFlushMode.ON_SAVE;

    public void save(RedisSession session) {
        session.saveDelta();
        if (session.isNew()) {
            String sessionCreatedKey = getSessionCreatedChannel(session.getId());
            this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);
            session.setNew(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;
    }

    private 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;
    }

再看一个类

代码语言:javascript
复制
public class DefaultCookieSerializer implements CookieSerializer {
    private String cookieName = "SESSION";

    private Boolean useSecureCookie;

    private boolean useHttpOnlyCookie = isServlet3();

    private String cookiePath;

    private int cookieMaxAge = -1;

    private String domainName;

    private Pattern domainNamePattern;

    private String jvmRoute;

    public List<String> readCookieValues(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        List<String> matchingCookieValues = new ArrayList<String>();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (this.cookieName.equals(cookie.getName())) {
                    String sessionId = cookie.getValue();
                    if (sessionId == null) {
                        continue;
                    }
                    if (this.jvmRoute != null && sessionId.endsWith(this.jvmRoute)) {
                        sessionId = sessionId.substring(0,
                                sessionId.length() - this.jvmRoute.length());
                    }
                    matchingCookieValues.add(sessionId);
                }
            }
        }
        return matchingCookieValues;
    }

    public void writeCookieValue(CookieValue cookieValue) {
        HttpServletRequest request = cookieValue.getRequest();
        HttpServletResponse response = cookieValue.getResponse();

        String requestedCookieValue = cookieValue.getCookieValue();
        String actualCookieValue = this.jvmRoute == null ? requestedCookieValue
                : requestedCookieValue + this.jvmRoute;

        Cookie sessionCookie = new Cookie(this.cookieName, actualCookieValue);
        sessionCookie.setSecure(isSecureCookie(request));
        sessionCookie.setPath(getCookiePath(request));
        String domainName = getDomainName(request);
        if (domainName != null) {
            sessionCookie.setDomain(domainName);
        }

        if (this.useHttpOnlyCookie) {
            sessionCookie.setHttpOnly(true);
        }

        if ("".equals(requestedCookieValue)) {
            sessionCookie.setMaxAge(0);
        }
        else {
            sessionCookie.setMaxAge(this.cookieMaxAge);
        }

        response.addCookie(sessionCookie);
    }

    /**
     * Sets if a Cookie marked as secure should be used.
     */
    public void setUseSecureCookie(boolean useSecureCookie) {
        this.useSecureCookie = useSecureCookie;
    }

    /**
     * Sets if a Cookie marked as HTTP Only should be used. The default is true in Servlet
     * 3+ environments, else false.
     *
     * @param useHttpOnlyCookie determines if the cookie should be marked as HTTP Only.
     */
    public void setUseHttpOnlyCookie(boolean useHttpOnlyCookie) {
        if (useHttpOnlyCookie && !isServlet3()) {
            throw new IllegalArgumentException(
                    "You cannot set useHttpOnlyCookie to true in pre Servlet 3 environment");
        }
        this.useHttpOnlyCookie = useHttpOnlyCookie;
    }

    private boolean isSecureCookie(HttpServletRequest request) {
        if (this.useSecureCookie == null) {
            return request.isSecure();
        }
        return this.useSecureCookie;
    }

    /**
     * Returns true if the Servlet 3 APIs are detected.
     */
    private boolean isServlet3() {
        try {
            ServletRequest.class.getMethod("startAsync");
            return true;
        }
        catch (NoSuchMethodException e) {
        }
        return false;
    }
}

再看SpringHttpSessionConfiguration内的CookieSerializer:

代码语言:javascript
复制
    @Autowired(required = false)
    public void setCookieSerializer(CookieSerializer cookieSerializer) {
        this.cookieSerializer = cookieSerializer;
    }

CookieSerializer 这个类的作用就是生成Cookie,然后设置到Response中,这样浏览器就会将这个Cookie的信息写入到客户端中,同时在session过期时会清除之前设置的cookie值。我们在自己的配置类中定义了一个Bean DefaultCookieSerializer,同时设置其cookiePath属性为”/”:

代码语言:javascript
复制
    /**
     * 自定义返回给前端的Cookie的项目根路径
     *
     * @return
     */
    @Bean
    public DefaultCookieSerializer defaultCookieSerializer() {
        DefaultCookieSerializer defaultCookieSerializer = new DefaultCookieSerializer();
        defaultCookieSerializer.setCookiePath("/");
        return defaultCookieSerializer;
    }

在这里需要注意,当我们没有自定义一个CookieSerializer时,Spring会使用默认的CookieSerializer,也就是直接new DefaultCookieSerializer()的形式,这样的话其设置Cookie的Path属性时会使用当前项目的根路径+”/”

代码语言:javascript
复制
public class DefaultCookieSerializer implements CookieSerializer {
    ...
    private String getCookiePath(HttpServletRequest request) {
        if (this.cookiePath == null) {
            return request.getContextPath() + "/";
        }
        return this.cookiePath;
    }
    ...
}

也就是说如果我当前项目的应用路径是/project,那么Cookie里的Path就是/project/ :

这里写图片描述

如果我的项目应用路径是/demo,那么Cookie的Path就是/demo/ 了。

在此处先说下客户端浏览器上的Cookie的作用原理,对于浏览器上的Cookie来说,Domain和Path是决定当前请求是否要携带这个Cookie的关键。如果当前请求的域名与Domain一致或是Domain的子域名,且域名后的应用名称与Path一致,那么当前请求就会携带上此Cookie的数据。如果两个选项有一个不符合,则不会带上Cookie的数据。

默认的CookieSerializer会设置Cookie的Path为当前应用路径加上”/”,也就是说如果我一台服务器上部署了多个子应用,不同应用的路径不同,则他们生成的Cookie的Path不同,那么就导致子应用间的Cookie不能共享,而子应用共享Cookie是实现SSO的关键,所以我们需要自定义一个CookieSerializer,让所有子应用的Path均相同。如果子应用不是部署在同一台服务器上,那么需要设置他们的Domain相同,使用统一的二级域名,比如baidu.com,csdn.cn等,同时设置其Path均相同,这样才是单点登录实现的关键。设置Cookie的Domain时有点要注意,只能设置为当前访问域名或者是其父级域名,若设置为子级域名则不会发生作用,若设置为其他域名,比如当前应用的域名为www.baidu.com,但你设置Domain为www.csdn.com,这种设置形式不能实现,这是浏览器对Cookie的一种限定。

接下来看RedisHttpSessionConfiguration这个配置类。在讲解其之前说点其他的。Spring-session不是只能整合Redis,还可以整合很多其他的,最直接的显示就是有多个和@EnableRedisHttpSession功能相同的注解,比如@EnableHazelcastHttpSession,@EnableJdbcHttpSession,@EnableMongoHttpSession,@EnableGemFireHttpSession,@EnableSpringHttpSession,其实他们的配置类都继承了SpringHttpSessionConfiguration,也就是说根本上还是通过封装Request,Response的形式改变获取Session的行为,感兴趣的可以自行查看他们的各自实现方法,这里只讲整合Redis的形式。

既然是整合Redis,那么肯定需要操作Redis的相关对象,RedisHttpSessionConfiguration这个配置类里定义的Bean基本都和Redis相关:

代码语言:javascript
复制
@Configuration
@EnableScheduling
public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration
        implements EmbeddedValueResolverAware, ImportAware {
    ......
    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(
            RedisConnectionFactory connectionFactory,
            RedisOperationsSessionRepository messageListener) {
        ......
        container.addMessageListener(messageListener,
                Arrays.asList(new PatternTopic("__keyevent@*:del"),
                        new PatternTopic("__keyevent@*:expired")));
        container.addMessageListener(messageListener, Arrays.asList(new PatternTopic(
                messageListener.getSessionCreatedChannelPrefix() + "*")));
        return container;
    }

    @Bean
    public RedisTemplate<Object, Object> sessionRedisTemplate(
            RedisConnectionFactory connectionFactory) {
        ......
    }

    @Bean
    public RedisOperationsSessionRepository sessionRepository(
            @Qualifier("sessionRedisTemplate") RedisOperations<Object, Object> sessionRedisTemplate,
            ApplicationEventPublisher applicationEventPublisher) {
        ......
    }

    @Autowired(required = false)
    @Qualifier("springSessionRedisTaskExecutor")
    public void setRedisTaskExecutor(Executor redisTaskExecutor) {
        this.redisTaskExecutor = redisTaskExecutor;
    }

    @Autowired(required = false)
    @Qualifier("springSessionRedisSubscriptionExecutor")
    public void setRedisSubscriptionExecutor(Executor redisSubscriptionExecutor) {
        this.redisSubscriptionExecutor = redisSubscriptionExecutor;
    }

    ......
}

RedisTemplate

public class WebContextInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    ......
    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] {new CharacterEncodingFilter("UTF-8", true),
            new DelegatingFilterProxy("springSessionRepositoryFilter")};
    }
}

Spring-session没有几个Bean,相关的组件基本都简略的介绍完了。

总结:

多个子应用必须有相同的父级域名(至少二级域名),或者域名相同,同时抹去返回Cookie的Path,实现共享Cookie,这点是实现单点登录(SSO)的前提。 注意尽量让Spring-session内的过滤器的位置靠前,因为他重新包装了Http请求的相关对象,所以在这个过滤器之后的Request,Response,Session对象都是其封装过后的对象,如果其之前的Filter里获取了Session等信息,那么他们和之后的Session就不是一个对象了。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.02.17 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档