当使用集群方式部署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方案重写一份了,这里简要总结一下开发过程。
这个就是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接口就是实现了对数据源操作的适配,这个是改造时必不可少的工作。
该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中。
我们不需要自己实现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