Spring-Session改造总结

Spring-Session改造总结

前言

当使用集群方式部署WEB服务时,访问请求可能会发到集群中的任何一台机器,如何在多台机器间共享session信息就会成为一个问题,而spring-session是个很好的解决方案。
spring-session以spring-session-core为核心,实现了以jdbc(也包括一些常见的数据库)为存储方案的spring-session-jdbc,以及以redis为存储方案的spring-session-data-redis,还有一个hazelcast(这个没玩过)。
个人认为redis、jdbc方案是能满足大部分的项目需求的,偏偏我们做项目需要使用的数据存储不在spring-session支持的列表范围内,只能参考redis和jdbc方案重写一份了,这里简要总结一下开发过程。

关键点1: 定义自己的SessionRepository

这个就是session管理的核心类了,session数据的操作都在该类实现。spring-session-core定义了FindByIndexNameSessionRepository接口,而该接口继承自 SessionRepositoy。

public interface SessionRepository {
    S createSession();
    void save(S session);
    S findById(String id);
    void deleteById(String id);
}

从SessionRepositoy接口的定义可以看出,该接口就是纯粹的增删改查,不意外。

public interface FindByIndexNameSessionRepository
        extends SessionRepository {  
    String PRINCIPAL_NAME_INDEX_NAME = FindByIndexNameSessionRepository.class.getName()
            .concat(".PRINCIPAL_NAME_INDEX_NAME");
    /**
    * Find a Map of the session id to the {@link Session} of all sessions that contain
    * the session attribute with the name
    * {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME} and the value of
    * the specified principal name.
    *
    */
    Map findByIndexNameAndIndexValue(String indexName, String indexValue);
}

而FindByIndexNameSessionRepository只是多定义了一个接口,看注释可能也会比较迷糊(熟悉spring-security的可能会根据principal猜到),一句话解释该接口的作用就是根据用户名反查该用户在线session集合,这个功能对于WEB应用的后台管理必不可少,查看哪些用户在线以及需要强制下线的时候,都需要用到该接口。 当然,该接口定义了两个参数,而不是单参数(用户名),应该有功能抽象和扩展性方面的考虑,只是目前不需要而已。redis和jdbc的函数实现都有一个判断可以看出该函数功能的单一:

if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) {
    return Collections.emptyMap();
}

实现FindByIndexNameSessionRepository接口就是实现了对数据源操作的适配,这个是改造时必不可少的工作。

关键点2: 定义sessionRepository bean

该bean定义后,Spring就可以使用自定义SessionRepository进行session的管理。 在jdbc和redis中,该定义都放在config.annotation.web.http的Configuration中:

JdbcHttpSessionConfiguration:
@Bean
public JdbcOperationsSessionRepository sessionRepository() {
    ...
};

RedisHttpSessionConfiguration:
@Bean
public RedisOperationsSessionRepository sessionRepository() {
    ...
};

当然让Spring使用自定义SessionRepository这个过程是间接的,是通过spring-session-core中的SessionRepositoryFilter实现,SessionRepositoryFilter处于filter调用链的前端,通过各种wrapper封装,将对session的各种操作最终都调用到自定义SessionRepository中。

关键点3: 定义sessionRegistry bean

我们不需要自己实现SessionRegistry接口,spring-session-core中已经定义了默认的实现SpringSessionBackedSessionRegistry,但是我们需要定义sessionRegistry bean以及设置。
(这个定义在jdbc和redis项目中并没有体现,可能是因为该bean与spring-security的设置有关,不应该在spring-session中预设,而spring-session提供默认实现,只是方便开发人员方便使用而已。)

具体使用类似:

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private FindByIndexNameSessionRepository sessionRepository;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            // other config goes here...
            .sessionManagement()
                .maximumSessions(2)
                .sessionRegistry(sessionRegistry());
        // @formatter:on
    }

    @Bean
    SpringSessionBackedSessionRegistry sessionRegistry() {
        return new SpringSessionBackedSessionRegistry<>(this.sessionRepository);
    }
}

可以看出sessionRegistry主要是为了与spring-security融合,需要注意的是,如果使用了自定义的SessionRepository并且需要进行Session的并发管理(调用了maximumSessions),则HttpSecurity的sessionRegistry必须要设置,否则spring-security会使用默认的SessionRegistry实现来进行session管理, 默认实现当然是不支持集群了。

结束语

定制一个spring-session,只需要注意以上三点,就可以使之正常运行。

参考资料:
[1] spring-session
[2] spring-session source code

你可能感兴趣的:(spring)