# `HttpSession`积分

Spring Session 提供了与`HttpSession`的透明集成。这意味着开发人员可以使用 Spring Session 支持的实现来切换`HttpSession`实现。

## 为什么 Spring session 和`HttpSession`？

我们已经提到， Spring Session 提供了与`HttpSession`的透明集成，但是我们从中得到了什么好处呢？

* **群集会话**: Spring Session 使得在不绑定到特定于应用程序容器的解决方案的情况下支持[群集会话](#httpsession-redis)变得非常简单。

* **RESTful API**: Spring session 让在 header 中提供会话 ID 可以与[RESTful API](#httpsession-rest)一起工作

## `HttpSession`with redis

使用 Spring session with`HttpSession`是通过在使用`HttpSession`之前添加一个 Servlet 过滤器来实现的。你可以通过以下两种方式选择启用此功能：

* [基于 Java 的配置](#httpsession-redis-jc)

* [基于 XML 的配置](#httpsession-redis-xml)

### Redis 基于 Java 的配置

这一节描述了如何使用基于 Java 的配置使用 Redis 来支持`HttpSession`。

|   |[HttpSession 示例](samples.html#samples)提供了如何使用 Java 配置集成 Spring Session 和`HttpSession`的工作示例，<br/>你可以在接下来的几节中阅读集成的基本步骤，但是，我们鼓励你在与自己的应用程序集成时遵循详细的 HttpSession 指南。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

####  Spring Java 配置

在添加了所需的依赖关系之后，我们就可以创建我们的 Spring 配置了。 Spring 配置负责创建一个 Servlet 过滤器，该过滤器将`HttpSession`实现替换为由 Spring Session 支持的实现。为此，添加以下 Spring 配置：

```
@EnableRedisHttpSession (1)
public class Config {

	@Bean
	public LettuceConnectionFactory connectionFactory() {
		return new LettuceConnectionFactory(); (2)
	}

}
```

|**1**|`@EnableRedisHttpSession`注释创建了一个名为`springSessionRepositoryFilter`的 Spring  Bean，它实现了`Filter`。<br/>过滤器负责替换要由 Spring Session 支持的`HttpSession`实现。<br/>在此实例中， Spring Session 由 Redis 支持。|
|-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|我们创建一个`RedisConnectionFactory`将 Spring Session 连接到 Redis 服务器。<br/>我们将连接配置为在默认端口（6379）上连接到 localhost。<br/>有关配置 Spring 数据 Redis 的更多信息，请参见[参考文献](https://docs.spring.io/spring-data/data-redis/docs/2.6.2/reference/html/)。|

#### Java Servlet 容器初始化

我们的[Spring Configuration](#httpsession-spring-configuration)创建了一个名为`springSessionRepositoryFilter`的 Spring  Bean，它实现了`Filter`。`springSessionRepositoryFilter` Bean 负责用 Spring Session 支持的自定义实现替换`HttpSession`。

为了让我们的`Filter`发挥其魔力， Spring 需要加载我们的`Config`类。最后，我们需要确保我们的 Servlet 容器（即 Tomcat）为每个请求使用我们的`springSessionRepositoryFilter`。幸运的是， Spring Session 提供了一个名为`AbstractHttpSessionApplicationInitializer`的实用程序类，以使这两个步骤都变得容易。以下是一个例子：

SRC/main/java/sample/initializer.java

```
public class Initializer extends AbstractHttpSessionApplicationInitializer { (1)

	public Initializer() {
		super(Config.class); (2)
	}

}
```

|   |我们类的名称（`Initializer`）并不重要。重要的是，我们扩展`AbstractHttpSessionApplicationInitializer`。|
|---|---------------------------------------------------------------------------------------------------------------------------------------|

|**1**|第一步是扩展`AbstractHttpSessionApplicationInitializer`。<br/>这样做可以确保 Spring  Bean 以`springSessionRepositoryFilter`的名称在我们的 Servlet 容器中为每个请求注册。|
|-----|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|`AbstractHttpSessionApplicationInitializer`还提供了一种机制，以确保 Spring 加载我们的`Config`。|

### 基于 XML 的 Redis 配置

本节介绍如何使用基于 XML 的配置使用 Redis 来支持`HttpSession`。

|   |[HttpSession XML 示例](samples.html#samples)提供了如何使用 XML 配置集成 Spring Session 和`HttpSession`的工作示例，<br/>你可以在接下来的几个部分中阅读集成的基本步骤，但是，我们鼓励你在与自己的应用程序集成时遵循详细的 HttpSession XML 指南。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

####  Spring xml 配置

在添加了所需的依赖关系之后，我们就可以创建我们的 Spring 配置了。 Spring 配置负责创建一个 Servlet 过滤器，该过滤器将`HttpSession`实现替换为由 Spring Session 支持的实现。为此，添加以下 Spring 配置：

SRC/main/webapp/WEB-INF/ Spring/session.xml

```
(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"/>
```

|**1**|我们使用`<context:annotation-config/>`和`RedisHttpSessionConfiguration`的组合，因为 Spring Session 尚未提供 XML 名称空间支持（参见[gh-104](https://github.com/spring-projects/spring-session/issues/104)）。<br/>这将创建一个名称为`springSessionRepositoryFilter`的 Spring  Bean 实现`Filter`。<br/>过滤器负责替换要由 Spring Session 支持的`HttpSession`实现。<br/>在此实例中， Spring Session 由 Redis 支持。|
|-----|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|我们创建一个`RedisConnectionFactory`将 Spring Session 连接到 Redis 服务器。<br/>我们将连接配置为在默认端口（6379）<br/>上连接到 localhost。有关配置 Spring 数据 Redis 的更多信息，请参见[参考文献](https://docs.spring.io/spring-data/data-redis/docs/2.6.2/reference/html/)。|

#### xml Servlet 容器初始化

我们的[Spring Configuration](#httpsession-xml-spring-configuration)创建了一个名为`springSessionRepositoryFilter`的 Spring  Bean，它实现了`Filter`。`springSessionRepositoryFilter` Bean 负责用 Spring Session 支持的自定义实现替换`HttpSession`。

为了使我们的`Filter`发挥其魔力，我们需要指示 Spring 加载我们的`session.xml`配置。我们可以通过以下配置来实现这一点：

SRC/main/webapp/WEB-INF/web.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`]（https://DOCS. Spring.io/ Spring/DOCS/5.3.16/ Spring-framework-reference/core.html#context-create）读取 contextconfiglocation 并获取 session.xml 配置。

最后，我们需要确保我们的 Servlet 容器（即 Tomcat）为每个请求使用我们的`springSessionRepositoryFilter`。下面的代码片段为我们执行了最后一步：

SRC/main/webapp/WEB-INF/web.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`]（https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/web/filter/delegatingfilterproxy.html）以`springSessionRepositoryFilter`的名称查找 Bean 并将其强制转换为`Filter`。对于每一个调用`DelegatingFilterProxy`的请求，都会调用`springSessionRepositoryFilter`。

### HttpSession with Mongo

使用 Spring 与`HttpSession`会话是通过在使用`HttpSession`的任何之前添加一个 Servlet 过滤器来实现的。

本节介绍如何使用基于 Java 的配置使用 Mongo 返回`HttpSession`。

|   |[HttpSession Mongo 示例](#samples)提供了一个工作示例，说明如何使用 Java 配置集成 Spring Session 和`HttpSession`。<br/>你可以阅读下面的集成基本步骤，但鼓励你在与自己的应用程序集成时遵循详细的 HttpSession 指南。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

你所要做的就是添加以下 Spring 配置：

```
@EnableMongoHttpSession (1)
public class HttpSessionConfig {

	@Bean
	public JdkMongoSessionConverter jdkMongoSessionConverter() {
		return new JdkMongoSessionConverter(Duration.ofMinutes(30)); (2)
	}

}
```

|**1**|`@EnableMongoHttpSession`注释创建了一个名为`springSessionRepositoryFilter`的 Spring  Bean，它实现了 filter。<br/>这个过滤器用 MongoDB 支持的 Bean 替换了默认的`HttpSession`。|
|-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|将会话超时时间配置为 30 分钟。|

#### 会话序列化机制

为了能够在 MongoDB 中持久化会话对象，我们需要提供序列化/反序列化机制。

默认情况下， Spring Session  MongoDB 将使用`JdkMongoSessionConverter`。

但是，只需在启动应用程序中添加以下代码，就可以切换到`JacksonMongoSessionConverter`:

```
@Bean
JacksonMongoSessionConverter mongoSessionConverter() {
    return new JacksonMongoSessionConverter();
}
```

##### JacksonMongosessionConverter

这种机制使用 Jackson 序列化到/来自 JSON 的会话对象。

通过创建以下内容 Bean:

```
@Bean
JacksonMongoSessionConverter mongoSessionConverter() {
    return new JacksonMongoSessionConverter();
}
```

…你可以从默认的（基于 JDK 的序列化）切换到使用 Jackson。

|   |如果你正在与 Spring Security 集成（通过将你的会话存储在 MongoDB 中），则该配置将<br/>注册适当的白名单组件，以便 Spring Security 能够正常工作。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

如果你想提供自定义的 Jackson 模块，你可以通过显式地注册如下所示的模块来实现这一点：

```
@Configuration
@EnableMongoHttpSession
static class Config extends BaseConfig {

	@Bean
	AbstractMongoSessionConverter mongoSessionConverter() {
		return new JacksonMongoSessionConverter(Collections.singletonList(new GeoModule()));
	}

}
```

##### JDKMongosessionConverter

`JdkMongoSessionConverter`使用标准的 Java 序列化来持久化以二进制形式映射到 MongoDB 的会话属性。但是，诸如 ID、访问时间等标准会话元素仍然被写成一个普通的 Mongo 对象，并且可以在不需要额外的努力的情况下进行读取和查询。如果没有明确的`JdkMongoSessionConverter` Bean 定义，则使用`AbstractMongoSessionConverter`。

还有一个构造函数接受`Serializer`和`Deserializer`对象，允许你传递自定义的实现，这在你想要使用非默认的类加载器时尤其重要。

## `HttpSession`with JDBC

通过在使用`HttpSession`的任何内容之前添加一个 Servlet 过滤器，可以使用与`HttpSession`的 Spring Session 。你可以选择以下任何一种方式：

* [基于 Java 的配置](#httpsession-jdbc-jc)

* [基于 XML 的配置](#httpsession-jdbc-xml)

* [Spring Boot-based Configuration](#httpsession-jdbc-boot)

### 基于 Java 的 JDBC 配置

本节描述了在使用基于 Java 的配置时如何使用关系数据库来备份`HttpSession`。

|   |[HttpSession JDBC 示例](samples.html#samples)提供了如何通过使用 Java 配置来集成 Spring Session 和`HttpSession`的工作示例，<br/>你可以在接下来的几个部分中阅读集成的基本步骤，但我们鼓励你在与自己的应用程序集成时遵循详细的 HttpSession JDBC 指南。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

####  Spring Java 配置

在添加了所需的依赖关系之后，我们就可以创建我们的 Spring 配置了。 Spring 配置负责创建一个 Servlet 过滤器，该过滤器用 Spring Session 支持的实现替换`HttpSession`实现。为此，添加以下 Spring 配置：

```
@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)
	}

}
```

|**1**|`@EnableJdbcHttpSession`注释创建了一个名为`springSessionRepositoryFilter`的 Spring  Bean。<br/>该 Bean 实现了`Filter`。<br/>过滤器负责替换要由 Spring Session 支持的`HttpSession`实现。<br/>在此实例中， Spring Session 是由关系数据库支持的。|
|-----|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|我们创建了一个`dataSource`，它将 Spring Session 连接到 H2 数据库的嵌入式实例。<br/>我们通过使用 Spring Session 中包含的 SQL 脚本来配置 H2 数据库来创建数据库表。|
|**3**|我们创建一个`transactionManager`，它管理先前配置的`dataSource`的事务。|

有关如何配置与数据访问相关的问题的更多信息，请参见[Spring Framework Reference Documentation](https://docs.spring.io/spring/docs/5.3.16/spring-framework-reference/data-access.html)。

#### Java Servlet 容器初始化

我们的[Spring Configuration](#httpsession-jdbc-spring-configuration)创建了一个名为`springSessionRepositoryFilter`的 Spring  Bean，它实现了`Filter`。`springSessionRepositoryFilter` Bean 负责用 Spring Session 支持的自定义实现替换`HttpSession`。

为了让我们的`Filter`发挥其魔力， Spring 需要加载我们的`Config`类。最后，我们需要确保我们的 Servlet 容器（即 Tomcat）为每个请求使用我们的`springSessionRepositoryFilter`。幸运的是， Spring Session 提供了一个名为`AbstractHttpSessionApplicationInitializer`的实用程序类，以使这两个步骤都变得容易。下面的示例展示了如何做到这一点：

SRC/main/java/sample/initializer.java

```
public class Initializer extends AbstractHttpSessionApplicationInitializer { (1)

	public Initializer() {
		super(Config.class); (2)
	}

}
```

|   |类的名称（初始化器）并不重要。<br/>重要的是我们扩展`AbstractHttpSessionApplicationInitializer`。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------|

|**1**|第一步是扩展`AbstractHttpSessionApplicationInitializer`。<br/>这样做可以确保名为`springSessionRepositoryFilter`的 Spring  Bean 在我们的 Servlet 容器中为每个请求注册。|
|-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|`AbstractHttpSessionApplicationInitializer`还提供了一种机制，以确保 Spring 加载我们的`Config`。|

#### 多个数据源

Spring Session 提供了`@SpringSessionDataSource`限定符，允许你显式地声明应该在`JdbcIndexedSessionRepository`中注入哪个`DataSource` Bean。这在应用程序上下文中存在多个`DataSource`bean 的场景中特别有用。

下面的示例展示了如何做到这一点：

config.java

```
@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() {
		// ...
	}
}
```

|**1**|此限定符声明 Spring Session 将使用 FirstDataSource。|
|-----|-----------------------------------------------------------------------------|

### 基于 XML 的 JDBC 配置

本节描述了在使用基于 XML 的配置时如何使用关系数据库来备份`HttpSession`。

|   |[HttpSession JDBC XML 示例](samples.html#samples)提供了如何通过使用 XML 配置来集成 Spring Session 和`HttpSession`的工作示例，<br/>你可以在接下来的几个部分中阅读集成的基本步骤，但是，我们鼓励你在与自己的应用程序集成时遵循详细的 HttpSession JDBC XML 指南。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

####  Spring xml 配置

在添加了所需的依赖关系之后，我们可以创建我们的 Spring 配置。 Spring 配置负责创建一个 Servlet 过滤器，该过滤器用 Spring Session 支持的实现替换`HttpSession`实现。下面的清单显示了如何添加以下 Spring 配置：

SRC/main/webapp/WEB-INF/ Spring/session.xml

```
(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>
```

|**1**|我们使用`<context:annotation-config/>`的组合和`JdbcHttpSessionConfiguration`因为 Spring session 尚未提供 XML 名称空间支持（参见[gh-104](https://github.com/spring-projects/spring-session/issues/104)）。<br/>这将创建一个名为`springSessionRepositoryFilter`的 Spring  Bean。<br/> Bean 实现`Filter`。<br/>过滤器负责替换要由 Spring session 支持的`HttpSession`实现。<br/>在此实例中， Spring Session 由关系数据库支持。|
|-----| |
|**2**|我们创建一个`dataSource`，它将 Spring Session 连接到 H2 数据库的嵌入式实例。<br/>我们通过使用 Spring Session 中包含的 SQL 脚本配置 H2 数据库来创建数据库表。|
|**3**|我们创建一个`transactionManager`，它管理先前配置的`dataSource`的事务。|

有关如何配置与数据访问相关的问题的更多信息，请参见[Spring Framework Reference Documentation](https://docs.spring.io/spring/docs/5.3.16/spring-framework-reference/data-access.html)。

#### xml Servlet 容器初始化

我们的[Spring Configuration](#httpsession-jdbc-xml-spring-configuration)创建了一个名为`springSessionRepositoryFilter`的 Spring  Bean，它实现了`Filter`。`springSessionRepositoryFilter` Bean 负责用 Spring Session 支持的自定义实现替换`HttpSession`。

为了使我们的`Filter`发挥其魔力，我们需要指示 Spring 加载我们的`session.xml`配置。我们的配置如下：

SRC/main/webapp/WEB-INF/web.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`]（https://DOCS. Spring.io/ Spring/DOCS/5.3.16/ Spring-framework-reference/core.html#context-create）读取`contextConfigLocation`并获取 session.xml 配置。

最后，我们需要确保我们的 Servlet 容器（即 Tomcat）为每个请求使用我们的`springSessionRepositoryFilter`。下面的代码片段为我们执行了最后一步：

SRC/main/webapp/WEB-INF/web.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`]（https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/web/filter/delegatingfilterproxy.html）查找一个名为`springSessionRepositoryFilter`的 Bean 并将其强制转换为`Filter`。对于每个调用`DelegatingFilterProxy`的请求，都调用`springSessionRepositoryFilter`。

### JDBC Spring 基于引导的配置

本节描述了在使用 Spring 引导时如何使用关系数据库来备份`HttpSession`。

|   |[ HttpSession JDBC Spring Boot Sample](samples.html#samples)提供了如何使用 Spring boot 集成 Spring Session 和`HttpSession`的工作示例，<br/>你可以在接下来的几个部分中阅读集成的基本步骤，但是，我们鼓励你在与自己的应用程序集成时遵循详细的 HttpSession JDBC Spring 引导指南。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

####  Spring 引导配置

在添加了所需的依赖项之后，我们可以创建我们的 Spring 引导配置。多亏了一流的自动配置支持，在关系数据库支持下设置 Spring Session 就像向`application.properties`添加一个配置属性一样简单。下面的清单展示了如何做到这一点：

SRC/主/资源/应用程序.properties

```
spring.session.store-type=jdbc # Session store type.
```

如果在 Classpath 上存在单个 Spring Session 模块，则 Spring 引导自动使用该存储实现。如果有多个实现，则必须选择要用来存储会话的 StoreType，如上面所示。

Spring Boot 应用的配置相当于手动添加`@EnableJdbcHttpSession`注释。这将创建一个名为`springSessionRepositoryFilter`的 Spring  Bean。 Bean 实现`Filter`。过滤器负责替换要由 Spring Session 支持的`HttpSession`实现。

你可以使用`application.properties`来进一步自定义。下面的清单展示了如何做到这一点：

SRC/主/资源/应用程序.properties

```
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/[email protected]@[email protected]@.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.
```

有关更多信息，请参见 Spring 引导文档的[Spring Session](https://docs.spring.io/spring-boot/docs/2.5.6/reference/htmlsingle/#boot-features-session)部分。

#### 配置`DataSource`

Spring 启动会自动创建`DataSource`，该会话将 Spring Session 连接到 H2 数据库的嵌入式实例。在生产环境中，你需要更新配置以指向关系数据库。例如，你可以在应用程序中包含以下内容：

SRC/主/资源/应用程序.properties

```
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 引导文档的[配置数据源](https://docs.spring.io/spring-boot/docs/2.5.6/reference/htmlsingle/#boot-features-configure-datasource)部分。

####  Servlet 容器初始化

我们的[Spring Boot Configuration](#httpsession-jdbc-boot-spring-configuration)创建了一个名为`springSessionRepositoryFilter`的 Spring  Bean，它实现了`Filter`。`springSessionRepositoryFilter` Bean 负责用 Spring Session 支持的自定义实现替换`HttpSession`。

为了让我们的`Filter`发挥其魔力， Spring 需要加载我们的`Config`类。最后，我们需要确保我们的 Servlet 容器（即 Tomcat）为每个请求使用我们的`springSessionRepositoryFilter`。幸运的是，Boot 为我们解决了这两个步骤。

## HttpSession with Hazelcast

使用 Spring 与`HttpSession`会话是通过在任何使用`HttpSession`的之前添加一个 Servlet 过滤器来实现的。

本节介绍如何使用基于 Java 的配置使用 HazelCast 来支持`HttpSession`。

|   |[ Hazelcast Spring Sample](samples.html#samples)提供了如何通过使用 Java 配置来集成 Spring Session 和`HttpSession`的工作示例，<br/>你可以在接下来的几个部分中阅读集成的基本步骤，但是，我们鼓励你在与自己的应用程序集成时遵循详细的 Hazelcast Spring 指南。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

###  Spring 配置

在添加了所需的依赖关系之后，我们可以创建我们的 Spring 配置。 Spring 配置负责创建 Servlet 过滤器，该过滤器用 Spring Session 支持的实现替换`HttpSession`实现。为此，添加以下 Spring 配置：

```
@EnableHazelcastHttpSession (1)
@Configuration
public class HazelcastHttpSessionConfig {

	@Bean
	public HazelcastInstance hazelcastInstance() {
		Config config = new Config();
		MapAttributeConfig attributeConfig = new MapAttributeConfig()
				.setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
				.setExtractor(PrincipalNameExtractor.class.getName());
		config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME) (2)
				.addMapAttributeConfig(attributeConfig).addMapIndexConfig(
						new MapIndexConfig(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
		SerializerConfig serializerConfig = new SerializerConfig();
		serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class);
		config.getSerializationConfig().addSerializerConfig(serializerConfig); (3)
		return Hazelcast.newHazelcastInstance(config); (4)
	}

}
```

|**1**|`@EnableHazelcastHttpSession`注释创建了一个名为`springSessionRepositoryFilter`的 Spring  Bean，它实现了`Filter`。<br/>过滤器负责替换要由 Spring Session 支持的`HttpSession`实现。<br/>在此实例中， Spring Session 由 Hazelcast 支持。|
|-----|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|为了支持通过主名索引检索会话，需要注册一个适当的`ValueExtractor`。<br/> Spring Session 为此提供了`PrincipalNameExtractor`。|
|**3**|为了有效地序列化`MapSession`对象，需要注册`HazelcastSessionSerializer`。如果未设置<br/>，HazelCast 将使用本机 Java 序列化来序列化会话。|
|**4**|我们创建一个`HazelcastInstance`将 Spring Session 连接到 Hazelcast。<br/>默认情况下，应用程序启动并连接到 Hazelcast 的嵌入式实例。<br/>有关配置 Hazelcast 的更多信息，请参见[参考文献](https://docs.hazelcast.org/docs/3.12.12/manual/html-single/index.html#hazelcast-configuration)。|

|   |如果`HazelcastSessionSerializer`是首选的，则需要在所有 Hazelcast 集群成员启动之前对其进行配置。<br/>在 Hazelcast 集群中，所有成员都应该对会话使用相同的序列化方法。此外，如果使用了 HazelCast 客户机/服务器拓扑<br/>，那么成员和客户机都必须使用相同的序列化方法。序列化器可以通过`ClientConfig`与`SerializerConfiguration`相同的成员来注册。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

###  Servlet 容器初始化

我们的[Spring Configuration](guides/java-security.html#security-spring-configuration)创建了一个名为`springSessionRepositoryFilter`的 Spring  Bean，它实现了`Filter`。`springSessionRepositoryFilter` Bean 负责用 Spring Session 支持的自定义实现替换`HttpSession`。

为了让我们的`Filter`发挥其魔力， Spring 需要加载我们的`SessionConfig`类。由于我们的应用程序已经通过使用`SecurityInitializer`类加载了 Spring 配置，因此我们可以将`SessionConfig`类添加到其中。下面的清单展示了如何做到这一点：

SRC/main/java/sample/securityinitializer.java

```
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {

	public SecurityInitializer() {
		super(SecurityConfig.class, SessionConfig.class);
	}

}
```

最后，我们需要确保我们的 Servlet 容器（即 Tomcat）为每个请求使用我们的`springSessionRepositoryFilter`。在 Spring security 的`springSecurityFilterChain`之前调用 Spring session 的`springSessionRepositoryFilter`是极其重要的。这样做可以确保 Spring 安全性使用的`HttpSession`得到 Spring Session 的支持。幸运的是， Spring Session 提供了一个名为`AbstractHttpSessionApplicationInitializer`的实用程序类，这使得这样做很容易。下面的示例展示了如何做到这一点：

SRC/main/java/sample/initializer.java

```
public class Initializer extends AbstractHttpSessionApplicationInitializer {

}
```

|   |我们类的名称（`Initializer`）并不重要。重要的是，我们扩展`AbstractHttpSessionApplicationInitializer`。|
|---|---------------------------------------------------------------------------------------------------------------------------------------|

通过扩展`AbstractHttpSessionApplicationInitializer`，我们确保在 Spring Security 的`springSecurityFilterChain`之前的每个请求都将名为`springSessionRepositoryFilter`的 Spring  Bean 注册到我们的 Servlet 容器中。

## 集成如何工作

幸运的是，`HttpSession`和`HttpServletRequest`（获取`HttpSession`的 API）都是接口。这意味着我们可以为这些 API 中的每一个提供我们自己的实现。

|   |本节描述 Spring Session 如何提供与`HttpSession`的透明集成。我们提供这些内容，这样你就可以了解幕后发生了什么。这个功能已经集成了，你不需要自己实现这个逻辑。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

首先，我们创建一个自定义的`HttpServletRequest`，它返回一个`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 一起工作。

|   |[剩余样本](samples.html#samples)提供了一个工作示例，说明如何在 REST 应用程序中使用 Spring Session 来支持使用头部进行身份验证，<br/>你可以遵循下面几节中描述的集成的基本步骤，但是，我们鼓励你在与自己的应用程序集成时遵循详细的 REST 指南。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

###  Spring 配置

在添加了所需的依赖关系之后，我们可以创建我们的 Spring 配置。 Spring 配置负责创建一个 Servlet 过滤器，该过滤器将`HttpSession`实现替换为由 Spring Session 支持的实现。为此，添加以下 Spring 配置：

```
@Configuration
@EnableRedisHttpSession (1)
public class HttpSessionConfig {

	@Bean
	public LettuceConnectionFactory connectionFactory() {
		return new LettuceConnectionFactory(); (2)
	}

	@Bean
	public HttpSessionIdResolver httpSessionIdResolver() {
		return HeaderHttpSessionIdResolver.xAuthToken(); (3)
	}

}
```

|**1**|`@EnableRedisHttpSession`注释创建了一个名为`springSessionRepositoryFilter`的 Spring  Bean，它实现了`Filter`。<br/>过滤器负责替换要由 Spring Session 支持的`HttpSession`实现。<br/>在此实例中， Spring Session 由 Redis 支持。|
|-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|我们创建一个`RedisConnectionFactory`将 Spring Session 连接到 Redis 服务器。<br/>我们将连接配置为在默认端口（6379）上连接到 localhost。<br/>有关配置 Spring 数据 Redis 的更多信息，请参见[参考文献](https://docs.spring.io/spring-data/data-redis/docs/2.6.2/reference/html/)。|
|**3**|我们定制了 Spring Session 的 HttpSession 集成，以使用 HTTP 头来传递当前会话信息，而不是 Cookie。|

###  Servlet 容器初始化

我们的[Spring Configuration](#rest-spring-configuration)创建了一个名为`springSessionRepositoryFilter`的 Spring  Bean，它实现了`Filter`。`springSessionRepositoryFilter` Bean 负责用 Spring Session 支持的自定义实现替换`HttpSession`。

为了使我们的`Filter`发挥其魔力， Spring 需要加载我们的`Config`类。我们在我们的 Spring `MvcInitializer`中提供了配置，如下例所示：

SRC/main/java/sample/mvc/mvcinitializer.java

```
@Override
protected Class<?>[] getRootConfigClasses() {
	return new Class[] { SecurityConfig.class, HttpSessionConfig.class };
}
```

最后，我们需要确保我们的 Servlet 容器（即 Tomcat）为每个请求使用我们的`springSessionRepositoryFilter`。幸运的是， Spring Session 提供了一个名为`AbstractHttpSessionApplicationInitializer`的实用程序类，这使得这样做很容易。为此，使用默认构造函数扩展类，如下例所示：

SRC/main/java/sample/initializer.java

```
public class Initializer extends AbstractHttpSessionApplicationInitializer {

}
```

|   |我们类的名称（`Initializer`）并不重要。重要的是，我们扩展`AbstractHttpSessionApplicationInitializer`。|
|---|---------------------------------------------------------------------------------------------------------------------------------------|

## 使用`HttpSessionListener`

Spring 通过声明`SessionEventHttpSessionListenerAdapter`将`SessionDestroyedEvent`和`SessionCreatedEvent`转换为`HttpSessionEvent`，会话支持`HttpSessionListener`。要使用此支持，你需要：

* 确保你的`SessionRepository`实现支持并配置为发射`SessionDestroyedEvent`和`SessionCreatedEvent`。

* 将`SessionEventHttpSessionListenerAdapter`配置为 Spring  Bean。

* 将每个`HttpSessionListener`注入`SessionEventHttpSessionListenerAdapter`

如果你使用[`HttpSession`with Redis]（#HttpSession-Redis）中记录的配置支持，那么你所需要做的就是将每个`HttpSessionListener`注册为 Bean。例如，假设你希望支持 Spring Security 的并发控制，并且需要使用`HttpSessionEventPublisher`。在这种情况下，可以将`HttpSessionEventPublisher`添加为 Bean。在 Java 配置中，这可能如下所示：

```
@Configuration
@EnableRedisHttpSession
public class RedisHttpSessionConfig {

	@Bean
	public HttpSessionEventPublisher httpSessionEventPublisher() {
		return new HttpSessionEventPublisher();
	}

	// ...

}
```

在 XML 配置中，这可能如下所示：

```
<bean class="org.springframework.security.web.session.HttpSessionEventPublisher"/>
```