1.传统Session与Spring Session对比

传统容器session与应用绑定,保存在应用内存中,与容器形成一对一关系,如果多应用时无法实现session共享,比如session中保存用户信息,Spring Session通过巧妙的方式将session保存到一个公共的区域,支持可配置化方式,实现SessionRepository接口,可将session保存到Redis、Jdbc、Mongo等,图1表示两者的区别

 

2. 先看下如何使用

2.1 在pom.xml加入依赖并安装redis服务

   <!--spring-session-core  和 spring-data-redis集成包 用于分布式session管理-->
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
    </dependency>
    <!-- spring boot redis starter 用于自动装配-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

2.2 在SpringBoot启动类加上自动装配参数

@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 60*30)
@SpringBootApplication
public class RedisTestApplication{
   public static void main(String[] args) {
        SpringApplication.run(RedisTestApplication.class, args);
    }
}

2.3 在application.yml上加上redis连接信息

spring:
  redis:
    database: 0
    host: 192.168.1.123
    port: 6379
    password: *******

3. 开始揭密

3.1 对类的继承关系有个了解
SessionRepositoryRequestWrapper 与 SessionRepositoryResponseWrapper 是SessionRepositoryFilter的内部类, SessionRepositoryRequestWrapper 通过继承HttpServletRequestWrapper,采用装饰者模式,来扩展已有功能

HttpServletRequestWrapper中持有一个HttpServletRequest对象,然后实现HttpServletRequest接口的所有方法,所有方法实现中都是调用持有的HttpServletRequest对象的相应的方法。继承HttpServletRequestWrapper 可以对其重写。
SessionRepositoryRequestWrapper继承HttpServletRequestWrapper,在构造方法中将原有的HttpServletRequest通过调用super完成对HttpServletRequestWrapper中持有的HttpServletRequest初始化赋值,然后重写和session相关的方法。这样就保证SessionRepositoryRequestWrapper的其他方法调用都是使用原有的HttpServletRequest的数据,只有session相关的是重写的逻辑。

Request相关类图

Response相关类图

 

3.2 开启自动装配

@EnableRedisHttpSession

3.3 开启
RedisHttpSessionConfiguration

#导入RedisHttpSessionConfiguration这个 Spring Bean
@Import(RedisHttpSessionConfiguration.class)
@Configuration
public @interface EnableRedisHttpSession

通过
RedisHttpSessionConfiguration 自动装配 RedisHttpSessionConfiguration

@Configuration
@EnableScheduling
public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration{
    #定义sessionRepository,些Bean实现了SessionRepository,这个就是管理session 贮藏的接口,不同的贮藏方式会对应不同的方式
    @Bean
    public RedisOperationsSessionRepository sessionRepository() {
        #创建内置RedisTemplate,如我们业务操作Redis,可配置这个Bean
        RedisTemplate<Object, Object> redisTemplate = createRedisTemplate();
        #调用构造法创建对象
        RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(redisTemplate);
        sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
        if (this.defaultRedisSerializer != null) {
          sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
        }
        #设置默认session失效时间
        sessionRepository
                     .setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
        if (StringUtils.hasText(this.redisNamespace)) {
                sessionRepository.setRedisKeyNamespace(this.redisNamespace);
        }
        #设置redis刷新模式
        sessionRepository.setRedisFlushMode(this.redisFlushMode);
        #设置redis操作的数据库槽,默认为0
        int database = resolveDatabase();
        sessionRepository.setDatabase(database);
        return sessionRepository;
    }
  
     //设置连接工厂采用 lettuce 还是 jedis,默认是 lettuce
    ``` java
    @Autowired
	public void setRedisConnectionFactory(
			@SpringSessionRedisConnectionFactory ObjectProvider<RedisConnectionFactory> springSessionRedisConnectionFactory,
			ObjectProvider<RedisConnectionFactory> redisConnectionFactory) {
            RedisConnectionFactory redisConnectionFactoryToUse = springSessionRedisConnectionFactory
                            .getIfAvailable();
            if (redisConnectionFactoryToUse == null) {
                    redisConnectionFactoryToUse = redisConnectionFactory.getObject();
            }
            this.redisConnectionFactory = redisConnectionFactoryToUse;
	}
    ```
}

3.4 通过Servlet Api既可拿到redis中session,而不是原来的HttpSession,使用方式不变,这就是Spring-session 采用适配器模式来达到目地

 HttpSession session = request.getSession(false);

3.4 session 贮藏对应的接口层定义,分别对应session的增删改查,由不同贮藏方式自我实现

public interface SessionRepository<S extends Session> {
    S createSession();
    void save(S session);
    S findById(String id);
    void deleteById(String id);
}
public interface FindByIndexNameSessionRepository<S extends Session>
		extends SessionRepository<S> {
}

3.5 通过redis方式实现的存储,这是redis操作session总入口

public class RedisOperationsSessionRepository implements
		FindByIndexNameSessionRepository<RedisOperationsSessionRepository.RedisSession>,
		MessageListener {
     #指向sessionTemplate            
     private final RedisOperations<Object, Object> sessionRedisOperations; 
     public RedisOperationsSessionRepository(
			RedisOperations<Object, Object> sessionRedisOperations) {
		Assert.notNull(sessionRedisOperations, "sessionRedisOperations cannot be null");
		this.sessionRedisOperations = sessionRedisOperations;
		this.expirationPolicy = new RedisSessionExpirationPolicy(sessionRedisOperations,
				this::getExpirationsKey, this::getSessionKey);
		configureSessionChannels();
	}
                
    #保存session到redis            
    @Override
    public void save(RedisSession session) {
            session.save();
            if (session.isNew()) {
                    String sessionCreatedKey = getSessionCreatedChannel(session.getId());
                    this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);
                    session.setNew(false);
            }
    }
    
    #内部类,通过装饰模式包装MapSession,实现对session操作
    final class RedisSession implements Session {
        private final MapSession cached;
        private Instant originalLastAccessTime;
        private Map<String, Object> delta = new HashMap<>();
        private boolean isNew;
        private String originalPrincipalName;
        private String originalSessionId;
        ...
        private void save() {
            saveChangeSessionId();
            saveDelta();
        }
        
        //开始保存session 中delta map数据到redis
        private void saveDelta() {
            if (this.delta.isEmpty()) {
                return;
            }
            String sessionId = getId();
            //调用底层redis connection 保存,hashmap值
            getSessionBoundHashOperations(sessionId).putAll(this.delta);
            ...
        }
    
    }
}

3.6 分析 SessionRepositoryFilter,这是进行请求拦截改写HttpSession的入口,这是一个标准的Filter,通过责任链方式进行请求和响应处理

@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {

    #这是核心的方法,对HttpServletRequest和HttpServletResponse进行包装,并改写session保存位置
    @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);

        try {
            //filter链式调用
            filterChain.doFilter(wrappedRequest, wrappedResponse);
        }
        finally {
            //确保resonse在返回给client之前,提交session
            wrappedRequest.commitSession();
        }
    }

}
    //内部类装饰HttpServletResponse
    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;
        }
    }
    //内部类装饰HttpServletRequest,HttpServletResponse
    private final class SessionRepositoryRequestWrapper
                            extends HttpServletRequestWrapper{
       private final HttpServletResponse response;
       private SessionRepositoryRequestWrapper(HttpServletRequest request,
				HttpServletResponse response, ServletContext servletContext) {
            super(request);
            this.response = response;
            this.servletContext = servletContext;
       }
       
       //提交session
       private void commitSession() {
            HttpSessionWrapper wrappedSession = getCurrentSession();
            if (wrappedSession == null) {
                if (isInvalidateClientSession()) {
                        SessionRepositoryFilter.this.httpSessionIdResolver.expireSession(this,
                                        this.response);
                }
            }
            else {
                S session = wrappedSession.getSession();
                //清除当前request对象中session
                clearRequestedSessionCache();
                //调用外部类的sessionRepository来保存session
                SessionRepositoryFilter.this.sessionRepository.save(session);
                String sessionId = session.getId();
                if (!isRequestedSessionIdValid()
                                || !sessionId.equals(getRequestedSessionId())) {
                            SessionRepositoryFilter.this.httpSessionIdResolver.setSessionId(this,
                                        this.response, sessionId);
                }
            }
        }
    }