HttpSession 集成
Spring Session 提供与 HttpSession 的透明集成。这意味着开发者可以将 HttpSession 实现替换为由 Spring Session 支撑的实现。
为什么选择 Spring Session 与 HttpSession?
我们已经提到 Spring Session 提供了与 HttpSession 的透明集成,但我们能从中获得什么好处呢?
- 集群会话:Spring Session 让支持集群会话变得非常简单,而且不绑定于任何特定应用容器的解决方案。
- RESTful API:Spring Session 让在请求头中提供会话 ID 的方式能够与 RESTful API 协同工作。
使用 Redis 作为 HttpSession 的存储
你可以在任何以下方式中使用 Spring Session 与 HttpSession:
基于 Java 的 Redis 配置
本节介绍如何使用基于 Java 的配置,以 Redis 作为 HttpSession 的存储。
HttpSession 示例 提供了一个可运行的示例,演示如何使用 Java 配置集成 Spring Session 与 HttpSession。你可以在接下来的几节中阅读集成的基本步骤,但我们鼓励你在将其集成到自己的应用程序时,跟随详细的 HttpSession 指南一起操作。
Spring Java 配置
添加所需依赖后,我们就可以创建 Spring 配置。Spring 配置负责创建一个 servlet 过滤器,用由 Spring Session 支撑的实现替换 HttpSession 实现。为此,添加以下 Spring 配置:
@Configuration(proxyBeanMethods = false)
@EnableRedisHttpSession 1
public class Config {
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory(); 2
}
}
@EnableRedisHttpSession注解创建了一个名为springSessionRepositoryFilter、实现了Filter的 Spring Bean。该过滤器负责替换HttpSession实现,使其由 Spring Session 支撑。在本例中,Spring Session 由 Redis 支撑。我们创建一个
RedisConnectionFactory,将 Spring Session 连接到 Redis 服务器。我们将连接配置为连接到 localhost 的默认端口(6379)。关于配置 Spring Data Redis 的更多信息,请参阅参考文档。
Java Servlet 容器初始化
我们的 Spring 配置 创建了一个名为 springSessionRepositoryFilter、实现了 Filter 的 Spring Bean。springSessionRepositoryFilter bean 负责用由 Spring Session 支撑的自定义实现替换 HttpSession。
为了让我们的 Filter 发挥作用,Spring 需要加载我们的 Config 类。最后,我们需要确保 Servlet 容器(即 Tomcat)对每个请求都使用我们的 springSessionRepositoryFilter。幸运的是,Spring Session 提供了一个名为 AbstractHttpSessionApplicationInitializer 的工具类,使这两步都变得容易。下面是一个示例:
public class Initializer extends AbstractHttpSessionApplicationInitializer { 1
public Initializer() {
super(Config.class); 2
}
}
我们类的名字(Initializer)并不重要。重要的是我们继承了 AbstractHttpSessionApplicationInitializer。
第一步是继承
AbstractHttpSessionApplicationInitializer。这样做确保了名为springSessionRepositoryFilter的 Spring Bean 会为每个请求注册到我们的 Servlet 容器中。AbstractHttpSessionApplicationInitializer还提供了一种机制,确保 Spring 加载我们的Config。
基于 XML 的 Redis 配置
本节介绍如何使用基于 XML 的配置,以 Redis 作为 HttpSession 的存储。
HttpSession XML 示例 提供了一个可运行的示例,演示如何使用 XML 配置集成 Spring Session 与 HttpSession。你可以在接下来的几节中阅读集成的基本步骤,但我们鼓励你在将其集成到自己的应用程序时,跟随详细的 HttpSession XML 指南一起操作。
Spring XML 配置
添加所需依赖后,我们就可以创建 Spring 配置。Spring 配置负责创建一个 servlet 过滤器,用由 Spring Session 支撑的实现替换 HttpSession 实现。为此,添加以下 Spring 配置:
// <1>
<context:annotation-config/>
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
// <2>
<bean class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"/>
我们使用
<context:annotation-config/>与RedisHttpSessionConfiguration的组合,是因为 Spring Session 尚未提供 XML 命名空间支持(参见 gh-104)。这会创建一个名为springSessionRepositoryFilter、实现了Filter的 Spring Bean。该过滤器负责替换HttpSession实现,使其由 Spring Session 支撑。在本例中,Spring Session 由 Redis 支撑。我们创建一个
RedisConnectionFactory,将 Spring Session 连接到 Redis 服务器。我们将连接配置为连接到 localhost 的默认端口(6379)。关于配置 Spring Data Redis 的更多信息,请参阅参考文档。
XML Servlet 容器初始化
我们的 Spring 配置 创建了一个名为 springSessionRepositoryFilter、实现了 Filter 的 Spring Bean。springSessionRepositoryFilter bean 负责用由 Spring Session 支撑的自定义实现替换 HttpSession。
为了让我们的 Filter 发挥作用,我们需要指示 Spring 加载我们的 session.xml 配置。我们可以通过以下配置来做到这一点:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/session.xml
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
ContextLoaderListener 读取 contextConfigLocation,并加载我们的 session.xml 配置。
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
DelegatingFilterProxy 按 springSessionRepositoryFilter 这个名字查找一个 Bean,并将其转换为 Filter。每当调用 DelegatingFilterProxy 处理每个请求时,都会调用 springSessionRepositoryFilter。
使用 JDBC 作为 HttpSession 的存储
你可以在任何以下方式中使用 Spring Session 与 HttpSession:
基于 Java 的 JDBC 配置
本节介绍在使用基于 Java 的配置时,如何使用关系数据库作为 HttpSession 的存储。
HttpSession JDBC 示例 提供了一个可运行的示例,演示如何使用 Java 配置集成 Spring Session 与 HttpSession。你可以在接下来的几节中阅读集成的基本步骤,但我们鼓励你在将其集成到自己的应用程序时,跟随详细的 HttpSession JDBC 指南一起操作。
Spring Java 配置
添加所需依赖后,我们就可以创建 Spring 配置。Spring 配置负责创建一个 Servlet 过滤器,用由 Spring Session 支撑的实现替换 HttpSession 实现。为此,添加以下 Spring 配置:
@Configuration(proxyBeanMethods = false)
@EnableJdbcHttpSession 1
public class Config {
@Bean
public EmbeddedDatabase dataSource() {
return new EmbeddedDatabaseBuilder() 2
.setType(EmbeddedDatabaseType.H2)
.addScript("org/springframework/session/jdbc/schema-h2.sql")
.build();
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource); 3
}
}
@EnableJdbcHttpSession注解创建了一个名为springSessionRepositoryFilter的 Spring Bean。该 Bean 实现了Filter。该过滤器负责替换HttpSession实现,使其由 Spring Session 支撑。在本例中,Spring Session 由关系数据库支撑。我们创建一个
dataSource,将 Spring Session 连接到 H2 数据库的嵌入式实例。我们配置 H2 数据库使用 Spring Session 自带的 SQL 脚本来创建数据库表。我们创建一个
transactionManager,为前面配置的dataSource管理事务。
有关如何配置与数据访问相关事项的更多信息,请参阅 Spring Framework 参考文档。
Java Servlet 容器初始化
我们的 Spring 配置 创建了一个名为 springSessionRepositoryFilter、实现了 Filter 的 Spring Bean。springSessionRepositoryFilter bean 负责用由 Spring Session 支撑的自定义实现替换 HttpSession。
为了让我们的 Filter 发挥作用,Spring 需要加载我们的 Config 类。最后,我们需要确保 Servlet 容器(即 Tomcat)对每个请求都使用我们的 springSessionRepositoryFilter。幸运的是,Spring Session 提供了一个名为 AbstractHttpSessionApplicationInitializer 的工具类,使这两步都变得容易。下面的示例展示了如何做到这一点:
public class Initializer extends AbstractHttpSessionApplicationInitializer { 1
public Initializer() {
super(Config.class); 2
}
}
我们类的名字(Initializer)并不重要。重要的是我们继承了 AbstractHttpSessionApplicationInitializer。
第一步是继承
AbstractHttpSessionApplicationInitializer。这样做确保了名为springSessionRepositoryFilter的 Spring Bean 会为每个请求注册到我们的 Servlet 容器中。AbstractHttpSessionApplicationInitializer还提供了一种机制,确保 Spring 加载我们的Config。
多 DataSource
Spring Session 提供了 @SpringSessionDataSource 限定符,让你可以显式声明应将哪个 DataSource bean 注入 JdbcIndexedSessionRepository。当应用上下文中存在多个 DataSource bean 时,这尤其有用。
下面的示例展示了如何做到这一点:
@EnableJdbcHttpSession
public class Config {
@Bean
@SpringSessionDataSource 1
public EmbeddedDatabase firstDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2).addScript("org/springframework/session/jdbc/schema-h2.sql").build();
}
@Bean
public HikariDataSource secondDataSource() {
// ...
}
}
该限定符声明 Spring Session 将使用 firstDataSource。
基于 XML 的 JDBC 配置
本节介绍在使用基于 XML 的配置时,如何使用关系数据库作为 HttpSession 的存储。
HttpSession JDBC XML 示例 提供了一个可运行的示例,演示如何使用 XML 配置集成 Spring Session 与 HttpSession。你可以在接下来的几节中阅读集成的基本步骤,但我们鼓励你在将其集成到自己的应用程序时,跟随详细的 HttpSession JDBC XML 指南一起操作。
Spring XML 配置
添加所需依赖后,我们就可以创建 Spring 配置。Spring 配置负责创建一个 servlet 过滤器,用由 Spring Session 支撑的实现替换 HttpSession 实现。下面的清单展示了如何添加以下 Spring 配置:
// <1>
<context:annotation-config/>
<bean class="org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration"/>
// <2>
<jdbc:embedded-database id="dataSource" database-name="testdb" type="H2">
<jdbc:script location="classpath:org/springframework/session/jdbc/schema-h2.sql"/>
</jdbc:embedded-database>
// <3>
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource"/>
</bean>
我们使用
<context:annotation-config/>与JdbcHttpSessionConfiguration的组合,是因为 Spring Session 尚未提供 XML 命名空间支持(参见 gh-104)。这会创建一个名为springSessionRepositoryFilter的 Spring Bean。该 Bean 实现了Filter。该过滤器负责替换HttpSession实现,使其由 Spring Session 支撑。在本例中,Spring Session 由关系数据库支撑。我们创建一个
dataSource,将 Spring Session 连接到 H2 数据库的嵌入式实例。我们配置 H2 数据库使用 Spring Session 自带的 SQL 脚本来创建数据库表。我们创建一个
transactionManager,为前面配置的dataSource管理事务。
有关如何配置与数据访问相关事项的更多信息,请参阅 Spring Framework 参考文档。
XML Servlet 容器初始化
我们的 Spring 配置 创建了一个名为 springSessionRepositoryFilter、实现了 Filter 的 Spring Bean。springSessionRepositoryFilter bean 负责用由 Spring Session 支撑的自定义实现替换 HttpSession。
为了让我们的 Filter 发挥作用,我们需要指示 Spring 加载我们的 session.xml 配置。我们通过以下配置来做到这一点:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/session.xml
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
ContextLoaderListener 读取 contextConfigLocation,并加载我们的 session.xml 配置。
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
DelegatingFilterProxy 按 springSessionRepositoryFilter 这个名字查找一个 Bean,并将其转换为 Filter。每当 DelegatingFilterProxy 处理每个请求时,都会调用 springSessionRepositoryFilter。
基于 Spring Boot 的 JDBC 配置
本节介绍在使用 Spring Boot 时,如何使用关系数据库作为 HttpSession 的存储。
HttpSession JDBC Spring Boot 示例 提供了一个可运行的示例,演示如何使用 Spring Boot 集成 Spring Session 与 HttpSession。你可以在接下来的几节中阅读集成的基本步骤,但我们鼓励你在将其集成到自己的应用程序时,跟随详细的 HttpSession JDBC Spring Boot 指南一起操作。
Spring Boot 配置
添加所需依赖后,我们就可以创建 Spring Boot 配置。得益于一流的自动配置支持,只需添加该依赖,Spring Boot 就会为我们搭好由关系数据库支撑的 Spring Session。
如果类路径中只有一个 Spring Session 模块,Spring Boot 会自动使用该存储实现。如果你有多个实现,则必须选择希望用来存储会话的 StoreType,如上所示。
在底层,Spring Boot 应用的配置等价于手动添加 @EnableJdbcHttpSession 注解。这会创建一个名为 springSessionRepositoryFilter 的 Spring Bean。该 Bean 实现了 Filter。该过滤器负责替换 HttpSession 实现,使其由 Spring Session 支撑。
server.servlet.session.timeout= # Session timeout. If a duration suffix is not specified, seconds are used.
spring.session.jdbc.initialize-schema=embedded # Database schema initialization mode.
spring.session.jdbc.schema=classpath:org/springframework/session/jdbc/schema-@@platform@@.sql # Path to the SQL file to use to initialize the database schema.
spring.session.jdbc.table-name=SPRING_SESSION # Name of the database table used to store sessions.
你还可以使用 application.properties 进行进一步自定义。下面的清单展示了如何做到这一点:
更多信息,请参阅 Spring Boot 文档中的 Spring Session 部分。
配置 DataSource
spring.datasource.url= # JDBC URL of the database.
spring.datasource.username= # Login username of the database.
spring.datasource.password= # Login password of the database.
更多信息,请参阅 Spring Boot 文档中的配置 DataSource 部分。
Servlet 容器初始化
我们的 Spring Boot 配置 创建了一个名为 springSessionRepositoryFilter、实现了 Filter 的 Spring Bean。springSessionRepositoryFilter bean 负责用由 Spring Session 支撑的自定义实现替换 HttpSession。
为了让我们的 Filter 发挥作用,Spring 需要加载我们的 Config 类。最后,我们需要确保 Servlet 容器(即 Tomcat)对每个请求都使用我们的 springSessionRepositoryFilter。幸运的是,Spring Boot 替我们处理了这两步。
HttpSession 集成的工作原理
幸运的是,HttpSession 和 HttpServletRequest(用于获取 HttpSession 的 API)都是接口。这意味着我们可以为这两个 API 各自提供自己的实现。
本节介绍 Spring Session 如何提供与 HttpSession 的透明集成。我们提供这些内容是为了让你了解底层发生的事情。该功能已经集成好了,你不需要自己实现这套逻辑。
public class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
public SessionRepositoryRequestWrapper(HttpServletRequest original) {
super(original);
}
public HttpSession getSession() {
return getSession(true);
}
public HttpSession getSession(boolean createNew) {
// create an HttpSession implementation from Spring Session
}
// ... other methods delegate to the original HttpServletRequest ...
}
任何返回 HttpSession 的方法都被重写了。所有其他方法都由 HttpServletRequestWrapper 实现,并委托给原始的 HttpServletRequest 实现。
我们使用一个名为 SessionRepositoryFilter 的 servlet Filter 来替换 HttpServletRequest 实现。下面的伪代码展示了它的工作原理:
public class SessionRepositoryFilter implements Filter {
public doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
SessionRepositoryRequestWrapper customRequest =
new SessionRepositoryRequestWrapper(httpRequest);
chain.doFilter(customRequest, response, chain);
}
// ...
}
通过将自定义的 HttpServletRequest 实现传入 FilterChain,我们确保在我们的 Filter 之后调用的任何对象都使用自定义的 HttpSession 实现。这正说明了为什么 Spring Session 的 SessionRepositoryFilter 必须放在任何与 HttpSession 交互的组件之前。
HttpSession 与 RESTful API
Spring Session 可以通过在请求头中提供会话来与 RESTful API 协同工作。
REST 示例 提供了一个可运行的示例,演示如何在 REST 应用程序中使用 Spring Session 来支持通过请求头进行认证。你可以按照接下来几节所述的基本集成步骤操作,但我们鼓励你在将其集成到自己的应用程序时,跟随详细的 REST 指南一起操作。
Spring 配置
添加所需依赖后,我们就可以创建 Spring 配置。Spring 配置负责创建一个 servlet 过滤器,用由 Spring Session 支撑的实现替换 HttpSession 实现。为此,添加以下 Spring 配置:
@Configuration
@EnableRedisHttpSession 1
public class HttpSessionConfig {
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory(); 2
}
@Bean
public HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken(); 3
}
}
@EnableRedisHttpSession注解创建了一个名为springSessionRepositoryFilter、实现了Filter的 Spring Bean。该过滤器负责替换HttpSession实现,使其由 Spring Session 支撑。在本例中,Spring Session 由 Redis 支撑。我们创建一个
RedisConnectionFactory,将 Spring Session 连接到 Redis 服务器。我们将连接配置为连接到 localhost 的默认端口(6379)。关于配置 Spring Data Redis 的更多信息,请参阅参考文档。我们自定义 Spring Session 的 HttpSession 集成,使其使用 HTTP 请求头而非 Cookie 来传递当前会话信息。
Servlet 容器初始化
我们的 Spring 配置 创建了一个名为 springSessionRepositoryFilter、实现了 Filter 的 Spring Bean。springSessionRepositoryFilter bean 负责用由 Spring Session 支撑的自定义实现替换 HttpSession。
为了让我们的 Filter 发挥作用,Spring 需要加载我们的 Config 类。我们在 Spring MvcInitializer 中提供配置,如下面的示例所示:
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { SecurityConfig.class, HttpSessionConfig.class };
}
public class Initializer extends AbstractHttpSessionApplicationInitializer {
}
我们类的名字(Initializer)并不重要。重要的是我们继承了 AbstractHttpSessionApplicationInitializer。
使用 HttpSessionListener
Spring Session 通过声明 SessionEventHttpSessionListenerAdapter,将 SessionDestroyedEvent 和 SessionCreatedEvent 转换为 HttpSessionEvent,从而支持 HttpSessionListener。要使用此支持,你需要:
- 确保你的
SessionRepository实现支持并已配置为触发SessionDestroyedEvent和SessionCreatedEvent。 - 将
SessionEventHttpSessionListenerAdapter配置为一个 Spring bean。 - 将每个
HttpSessionListener注入到SessionEventHttpSessionListenerAdapter中。
@Configuration
@EnableRedisHttpSession
public class RedisHttpSessionConfig {
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
// ...
}
在 XML 配置中,可能如下所示:
<bean class="org.springframework.security.web.session.HttpSessionEventPublisher"/>