This topic explains how Spring Session for VMware Tanzu GemFire provides transparent integration with javax.servlet.http.HttpSession
. This allows developers to replace the HttpSession
implementation with an implementation that is backed by Spring Session.
Spring Session enables multiple different data store providers, like Tanzu GemFire, to be plugged in to manage the HttpSession
state.
HttpSession Management with Tanzu GemFire
When VMware Tanzu GemFire is used with Spring Session, a web application’s javax.servlet.http.HttpSession
can be replaced with a clustered implementation managed by Tanzu GemFire and accessed using Spring Session’s API.
The two most common topologies for managing session state using Tanzu GemFire:
- Client-Server
- Peer-To-Peer (P2P)
Additionally, Tanzu GemFire supports site-to-site replication using WAN topology. The ability to configure and use the Tanzu GemFire WAN functionality is independent of Spring Session.
Tanzu GemFire Client-Server
The Client-Server topology is a common configuration choice among users when using Tanzu GemFire as a provider in Spring Session because a Tanzu GemFire server has significantly different and unique JVM heap requirements than compared to the application. Using a Client-Server topology enables an application to manage application state independently of other application processes.
In a Client-Server topology, an application using Spring Session will open one or more connections to a remote cluster of Tanzu GemFire servers that will manage access to all HttpSession
state.
You can configure a Client-Server topology with either:
-
Java-based Configuration
-
XML-based Configuration
Tanzu GemFire Client-Server Java-Based Configuration
This section describes how to configure the Tanzu GemFire Client-Server topology to manage HttpSession
state with Java-based configuration.
Spring Java Configuration
After adding the required dependencies and repository declarations, we can create the Spring configuration. The Spring configuration is responsible for creating a Servlet Filter
that replaces the HttpSession
with an implementation backed by Spring Session and Tanzu GemFire.
Client Configuration
@ClientCacheApplication(name = "SpringSessionDataGemFireJavaConfigSampleClient", logLevel = "error") //SEE COMMENT 1
@EnablePool(name = "subscriptionPool", readTimeout = 15000, retryAttempts = 1)
@EnableGemFireHttpSession(poolName = "subscriptionPool") //SEE COMMENT 2
public class ClientConfig extends ClientServerIntegrationTestsSupport {
@Bean
ClientCacheConfigurer gemfireServerReadyConfigurer( //SEE COMMENT 3
@Value("${spring.data.gemfire.cache.server.port:40404}") int cacheServerPort) {
return (beanName, clientCacheFactoryBean) -> waitForServerToStart("localhost", cacheServerPort);
}
}
Comments:
-
We declare our Web application to be an Tanzu GemFire cache client by annotating the ClientConfig
class with @ClientCacheApplication
-
@EnableGemFireHttpSession
creates a Spring bean named springSessionRepositoryFilter
that implements javax.servlet.Filter
. The filter replaces the HttpSession
with an implementation provided by Spring Session and backed by Tanzu GemFire. Additionally, the configuration creates the necessary client-side Region
corresponding to the same server-side Region
by name. By default, this is ClusteredSpringSessions
, which is a PROXY
Region
. All session state is sent from the client to the server through Region
data access operations. The client-side Region
use the “subscriptionPool” Pool
.
-
We wait to ensure the Tanzu GemFire Server is up and running before we proceed. This is used for automated integration testing purposes.
Enabling Tanzu GemFire HttpSession Management
The @EnableGemFireHttpSession
annotation enables developers to configure certain aspects of both Spring Session and Tanzu GemFire out-of-the-box using the following attributes:
-
clientRegionShortcut
: Specifies Tanzu GemFire
data management policy on the client with the ClientRegionShortcut
(see VMware GemFire Java API Reference). Default: PROXY
. This attribute is only used when configuring the client Region
.
indexableSessionAttributes
: Identifies the Session attributes by name that should be indexed for querying purposes. Only Session attributes explicitly identified by name will be indexed.
maxInactiveIntervalInSeconds
: Controls HttpSession idle-timeout expiration. Defaults to 30 minutes.
poolName
: Name of the dedicated Tanzu GemFire Pool
used to connect a client to the cluster of servers. This attribute is only used when the application is a cache client. Default: gemfirePool
.
regionName
: Specifies the name of the Tanzu GemFire Region
used to store and manage HttpSession
state. Default: ClusteredSpringSessions
.
serverRegionShortcut
: Specifies Tanzu GemFire data management policy on the server with the RegionShortcut
(see VMware GemFire Java API Reference). Default: PARTITION
. This attribute is only used when configuring server Regions
, or when a P2P topology is employed.
Note: The Tanzu GemFire client Region
name must match a
server Region
by the same name if the client Region
is a
PROXY
or CACHING_PROXY
. Client and server
Region
names are not required to match if the client
Region
used to store session state is
LOCAL.
When using LOCAL
Regions, Session state will not be propagated to the server.
Server Configuration
Create a Tanzu GemFire Server. The cache client communicates with the server and sends session state to the server to manage.
In this sample, the following Java configuration will be used to configure and run a Tanzu GemFire Server:
@CacheServerApplication(name = "SpringSessionDataGemFireJavaConfigSampleServer", logLevel = "error") //SEE COMMENT 1
@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = 30) //SEE COMMENT 2
public class GemFireServer {
@SuppressWarnings("resource")
public static void main(String[] args) {
new AnnotationConfigApplicationContext(GemFireServer.class).registerShutdownHook();
}
}
Comments
-
We use the @CacheServerApplication
annotation to simplify the creation of a peer cache instance containing with a CacheServer
for cache clients to connect.
-
(Optional) We use the @EnableGemFireHttpSession
annotation to create the necessary server-side Region
to store the HttpSessions
state. By default, this is ClusteredSpringSessions
. This step is optional because we could create the Session Region
manually.
Tanzu GemFire Client-Server using XML Configuration
This section describes how to configure the Tanzu GemFire Client-Server topology to manage HttpSession
state with XML-based configuration.
Spring XML Configuration
After adding the required dependencies and repository declarations, we can create the Spring configuration. The Spring configuration is responsible for creating a Servlet
Filter
that replaces the javax.servlet.http.HttpSession
with an implementation backed by Spring Session and Tanzu GemFire.
Client Configuration
Add the following Spring configuration:
<context:annotation-config/>
<context:property-placeholder/>
<bean class="sample.client.ClientServerReadyBeanPostProcessor"/>
<!--SEE COMMENT 1-->
<util:properties id="gemfireProperties">
<prop key="log-level">${spring.data.gemfire.cache.log-level:error}</prop>
</util:properties>
<!--SEE COMMENT 2-->
<gfe:client-cache properties-ref="gemfireProperties" pool-name="gemfirePool"/>
<!--SEE COMMENT 3-->
<gfe:pool name="subscriptionPool" read-timeout="15000" retry-attempts="1" subscription-enabled="true">
<gfe:server host="localhost" port="${spring.data.gemfire.cache.server.port:40404}"/>
</gfe:pool>
<!--SEE COMMENT 4-->
<bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"
p:poolName="subscriptionPool"/>
Comments:
-
(Optional) Include a Properties
bean to configure certain aspects of the Tanzu GemFire ClientCache
using GemFire Properties. In this case, we are setting the Tanzu GemFire log-level
using an application-specific System property, defaulting to warning
if unspecified.
-
Create an instance of an Tanzu GemFire ClientCache
. Initialize it with the gemfireProperties
.
-
Configure a Pool
of connections to communicate with the Tanzu GemFire Server in this Client-Server topology. The Pool
has been configured to connect directly to the server using the nested gfe:server
element.
-
A GemFireHttpSessionConfiguration
bean is registered to enable Spring Session functionality.
Server Configuration
We create a Tanzu GemFire Server. The cache client communicates with the server and sends session state to the server to manage.
In this sample, we use the following XML configuration to configure and run a Tanzu GemFire Server:
<context:annotation-config/>
<context:property-placeholder/>
<!--SEE COMMENT 1-->
<util:properties id="gemfireProperties">
<prop key="name">SpringSessionDataGemFireSampleXmlServer</prop>
<prop key="log-level">${spring.data.gemfire.cache.log-level:error}</prop>
</util:properties>
<!--SEE COMMENT 2-->
<gfe:cache properties-ref="gemfireProperties"/>
<!--SEE COMMENT 3-->
<gfe:cache-server port="${spring.data.gemfire.cache.server.port:40404}"/>
<!--SEE COMMENT 4-->
<bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"
p:maxInactiveIntervalInSeconds="30"/>
Comments:
-
(Optional) Include a Properties
bean to configure certain aspects of the Tanzu GemFire ClientCache
using GemFire Properties. In this case, we are setting the Tanzu GemFire log-level
using an application-specific System property, defaulting to warning
if unspecified.
-
Configure a Tanzu GemFire peer Cache
instance. Initialize it with the Tanzu GemFire properties.
-
Define a CacheServer
with sensible configuration for bind-address
and port
used by our cache client application to connect to the server and send session state.
-
Enable the same Spring Session functionality that was declared in the client XML configuration by registering an instance of GemFireHttpSessionConfiguration
. Set the session expiration timeout to 30 seconds.
Bootstrap the Tanzu GemFire Server with the following:
@Configuration //SEE COMMENT 1
@ImportResource("META-INF/spring/session-server.xml") //SEE COMMENT 1
public class GemFireServer {
public static void main(String[] args) {
new AnnotationConfigApplicationContext(GemFireServer.class).registerShutdownHook();
}
}
Comments:
-
The @Configuration
annotation designates this Java class as a source of Spring configuration metadata.
-
The configuration primarily comes from the META-INF/spring/session-server.xml
file.
XML Servlet Container Initialization
Spring XML Configuration created a Spring bean named springSessionRepositoryFilter
that implements the javax.servlet.Filter
interface. The springSessionRepositoryFilter
bean is responsible for replacing the javax.servlet.http.HttpSession
with a custom implementation that is provided by Spring Session and Tanzu GemFire.
The Filter
requires that we instruct Spring to load the session-client.xml
configuration file. We do this with the following configuration in src/main/webapp/WEB-INF/web.xml
:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/session-client.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
The ContextLoaderListener reads the contextConfigLocation
context parameter value and picks up the session-client.xml
configuration file. We must also ensure that the Servlet container, Tomcat, uses the springSessionRepositoryFilter
for every request.
The following in src/main/webapp/WEB-INF/web.xml
performs this last step:
<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>
The DelegatingFilterProxy will look up a bean by the name of springSessionRepositoryFilter
and cast it to a Filter
. For every HTTP request, the DelegatingFilterProxy
is invoked, which delegates to the springSessionRepositoryFilter
.
Filter
that replaces the HttpSession
with an implementation backed by Spring Session and Tanzu GemFire.@ClientCacheApplication(name = "SpringSessionDataGemFireJavaConfigSampleClient", logLevel = "error") //SEE COMMENT 1
@EnablePool(name = "subscriptionPool", readTimeout = 15000, retryAttempts = 1)
@EnableGemFireHttpSession(poolName = "subscriptionPool") //SEE COMMENT 2
public class ClientConfig extends ClientServerIntegrationTestsSupport {
@Bean
ClientCacheConfigurer gemfireServerReadyConfigurer( //SEE COMMENT 3
@Value("${spring.data.gemfire.cache.server.port:40404}") int cacheServerPort) {
return (beanName, clientCacheFactoryBean) -> waitForServerToStart("localhost", cacheServerPort);
}
}
Comments:
-
We declare our Web application to be an Tanzu GemFire cache client by annotating the
ClientConfig
class with@ClientCacheApplication
-
@EnableGemFireHttpSession
creates a Spring bean namedspringSessionRepositoryFilter
that implementsjavax.servlet.Filter
. The filter replaces theHttpSession
with an implementation provided by Spring Session and backed by Tanzu GemFire. Additionally, the configuration creates the necessary client-sideRegion
corresponding to the same server-sideRegion
by name. By default, this isClusteredSpringSessions
, which is aPROXY
Region
. All session state is sent from the client to the server throughRegion
data access operations. The client-sideRegion
use the “subscriptionPool”Pool
. -
We wait to ensure the Tanzu GemFire Server is up and running before we proceed. This is used for automated integration testing purposes.
Enabling Tanzu GemFire HttpSession Management
The @EnableGemFireHttpSession
annotation enables developers to configure certain aspects of both Spring Session and Tanzu GemFire out-of-the-box using the following attributes:
-
clientRegionShortcut
: Specifies Tanzu GemFire
ClientRegionShortcut
(see VMware GemFire Java API Reference). Default: PROXY
. This attribute is only used when configuring the client Region
.indexableSessionAttributes
: Identifies the Session attributes by name that should be indexed for querying purposes. Only Session attributes explicitly identified by name will be indexed.
maxInactiveIntervalInSeconds
: Controls HttpSession idle-timeout expiration. Defaults to 30 minutes.
poolName
: Name of the dedicated Tanzu GemFire Pool
used to connect a client to the cluster of servers. This attribute is only used when the application is a cache client. Default: gemfirePool
.
regionName
: Specifies the name of the Tanzu GemFire Region
used to store and manage HttpSession
state. Default: ClusteredSpringSessions
.
serverRegionShortcut
: Specifies Tanzu GemFire data management policy on the server with the RegionShortcut
(see VMware GemFire Java API Reference). Default: PARTITION
. This attribute is only used when configuring server Regions
, or when a P2P topology is employed.
Region
name must match a
server Region
by the same name if the client Region
is a
PROXY
or CACHING_PROXY
. Client and server
Region
names are not required to match if the client
Region
used to store session state is
LOCAL.
When using LOCAL
Regions, Session state will not be propagated to the server.@CacheServerApplication(name = "SpringSessionDataGemFireJavaConfigSampleServer", logLevel = "error") //SEE COMMENT 1
@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = 30) //SEE COMMENT 2
public class GemFireServer {
@SuppressWarnings("resource")
public static void main(String[] args) {
new AnnotationConfigApplicationContext(GemFireServer.class).registerShutdownHook();
}
}
We use the @CacheServerApplication
annotation to simplify the creation of a peer cache instance containing with a CacheServer
for cache clients to connect.
(Optional) We use the @EnableGemFireHttpSession
annotation to create the necessary server-side Region
to store the HttpSessions
state. By default, this is ClusteredSpringSessions
. This step is optional because we could create the Session Region
manually.
This section describes how to configure the Tanzu GemFire Client-Server topology to manage HttpSession
state with XML-based configuration.
Spring XML Configuration
After adding the required dependencies and repository declarations, we can create the Spring configuration. The Spring configuration is responsible for creating a Servlet
Filter
that replaces the javax.servlet.http.HttpSession
with an implementation backed by Spring Session and Tanzu GemFire.
Client Configuration
Add the following Spring configuration:
<context:annotation-config/>
<context:property-placeholder/>
<bean class="sample.client.ClientServerReadyBeanPostProcessor"/>
<!--SEE COMMENT 1-->
<util:properties id="gemfireProperties">
<prop key="log-level">${spring.data.gemfire.cache.log-level:error}</prop>
</util:properties>
<!--SEE COMMENT 2-->
<gfe:client-cache properties-ref="gemfireProperties" pool-name="gemfirePool"/>
<!--SEE COMMENT 3-->
<gfe:pool name="subscriptionPool" read-timeout="15000" retry-attempts="1" subscription-enabled="true">
<gfe:server host="localhost" port="${spring.data.gemfire.cache.server.port:40404}"/>
</gfe:pool>
<!--SEE COMMENT 4-->
<bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"
p:poolName="subscriptionPool"/>
Comments:
-
(Optional) Include a Properties
bean to configure certain aspects of the Tanzu GemFire ClientCache
using GemFire Properties. In this case, we are setting the Tanzu GemFire log-level
using an application-specific System property, defaulting to warning
if unspecified.
-
Create an instance of an Tanzu GemFire ClientCache
. Initialize it with the gemfireProperties
.
-
Configure a Pool
of connections to communicate with the Tanzu GemFire Server in this Client-Server topology. The Pool
has been configured to connect directly to the server using the nested gfe:server
element.
-
A GemFireHttpSessionConfiguration
bean is registered to enable Spring Session functionality.
Server Configuration
We create a Tanzu GemFire Server. The cache client communicates with the server and sends session state to the server to manage.
In this sample, we use the following XML configuration to configure and run a Tanzu GemFire Server:
<context:annotation-config/>
<context:property-placeholder/>
<!--SEE COMMENT 1-->
<util:properties id="gemfireProperties">
<prop key="name">SpringSessionDataGemFireSampleXmlServer</prop>
<prop key="log-level">${spring.data.gemfire.cache.log-level:error}</prop>
</util:properties>
<!--SEE COMMENT 2-->
<gfe:cache properties-ref="gemfireProperties"/>
<!--SEE COMMENT 3-->
<gfe:cache-server port="${spring.data.gemfire.cache.server.port:40404}"/>
<!--SEE COMMENT 4-->
<bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"
p:maxInactiveIntervalInSeconds="30"/>
Comments:
-
(Optional) Include a Properties
bean to configure certain aspects of the Tanzu GemFire ClientCache
using GemFire Properties. In this case, we are setting the Tanzu GemFire log-level
using an application-specific System property, defaulting to warning
if unspecified.
-
Configure a Tanzu GemFire peer Cache
instance. Initialize it with the Tanzu GemFire properties.
-
Define a CacheServer
with sensible configuration for bind-address
and port
used by our cache client application to connect to the server and send session state.
-
Enable the same Spring Session functionality that was declared in the client XML configuration by registering an instance of GemFireHttpSessionConfiguration
. Set the session expiration timeout to 30 seconds.
Bootstrap the Tanzu GemFire Server with the following:
@Configuration //SEE COMMENT 1
@ImportResource("META-INF/spring/session-server.xml") //SEE COMMENT 1
public class GemFireServer {
public static void main(String[] args) {
new AnnotationConfigApplicationContext(GemFireServer.class).registerShutdownHook();
}
}
Comments:
-
The @Configuration
annotation designates this Java class as a source of Spring configuration metadata.
-
The configuration primarily comes from the META-INF/spring/session-server.xml
file.
XML Servlet Container Initialization
Spring XML Configuration created a Spring bean named springSessionRepositoryFilter
that implements the javax.servlet.Filter
interface. The springSessionRepositoryFilter
bean is responsible for replacing the javax.servlet.http.HttpSession
with a custom implementation that is provided by Spring Session and Tanzu GemFire.
Servlet
Filter
that replaces the javax.servlet.http.HttpSession
with an implementation backed by Spring Session and Tanzu GemFire.<context:annotation-config/>
<context:property-placeholder/>
<bean class="sample.client.ClientServerReadyBeanPostProcessor"/>
<!--SEE COMMENT 1-->
<util:properties id="gemfireProperties">
<prop key="log-level">${spring.data.gemfire.cache.log-level:error}</prop>
</util:properties>
<!--SEE COMMENT 2-->
<gfe:client-cache properties-ref="gemfireProperties" pool-name="gemfirePool"/>
<!--SEE COMMENT 3-->
<gfe:pool name="subscriptionPool" read-timeout="15000" retry-attempts="1" subscription-enabled="true">
<gfe:server host="localhost" port="${spring.data.gemfire.cache.server.port:40404}"/>
</gfe:pool>
<!--SEE COMMENT 4-->
<bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"
p:poolName="subscriptionPool"/>
(Optional) Include a Properties
bean to configure certain aspects of the Tanzu GemFire ClientCache
using GemFire Properties. In this case, we are setting the Tanzu GemFire log-level
using an application-specific System property, defaulting to warning
if unspecified.
Create an instance of an Tanzu GemFire ClientCache
. Initialize it with the gemfireProperties
.
Configure a Pool
of connections to communicate with the Tanzu GemFire Server in this Client-Server topology. The Pool
has been configured to connect directly to the server using the nested gfe:server
element.
A GemFireHttpSessionConfiguration
bean is registered to enable Spring Session functionality.
<context:annotation-config/>
<context:property-placeholder/>
<!--SEE COMMENT 1-->
<util:properties id="gemfireProperties">
<prop key="name">SpringSessionDataGemFireSampleXmlServer</prop>
<prop key="log-level">${spring.data.gemfire.cache.log-level:error}</prop>
</util:properties>
<!--SEE COMMENT 2-->
<gfe:cache properties-ref="gemfireProperties"/>
<!--SEE COMMENT 3-->
<gfe:cache-server port="${spring.data.gemfire.cache.server.port:40404}"/>
<!--SEE COMMENT 4-->
<bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"
p:maxInactiveIntervalInSeconds="30"/>
(Optional) Include a Properties
bean to configure certain aspects of the Tanzu GemFire ClientCache
using GemFire Properties. In this case, we are setting the Tanzu GemFire log-level
using an application-specific System property, defaulting to warning
if unspecified.
Configure a Tanzu GemFire peer Cache
instance. Initialize it with the Tanzu GemFire properties.
Define a CacheServer
with sensible configuration for bind-address
and port
used by our cache client application to connect to the server and send session state.
Enable the same Spring Session functionality that was declared in the client XML configuration by registering an instance of GemFireHttpSessionConfiguration
. Set the session expiration timeout to 30 seconds.
@Configuration //SEE COMMENT 1
@ImportResource("META-INF/spring/session-server.xml") //SEE COMMENT 1
public class GemFireServer {
public static void main(String[] args) {
new AnnotationConfigApplicationContext(GemFireServer.class).registerShutdownHook();
}
}
The @Configuration
annotation designates this Java class as a source of Spring configuration metadata.
The configuration primarily comes from the META-INF/spring/session-server.xml
file.
The Filter
requires that we instruct Spring to load the session-client.xml
configuration file. We do this with the following configuration in src/main/webapp/WEB-INF/web.xml
:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/session-client.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
The ContextLoaderListener reads the contextConfigLocation
context parameter value and picks up the session-client.xml
configuration file. We must also ensure that the Servlet container, Tomcat, uses the springSessionRepositoryFilter
for every request.
The following in src/main/webapp/WEB-INF/web.xml
performs this last step:
<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>
The DelegatingFilterProxy will look up a bean by the name of springSessionRepositoryFilter
and cast it to a Filter
. For every HTTP request, the DelegatingFilterProxy
is invoked, which delegates to the springSessionRepositoryFilter
.
Tanzu GemFire Peer-To-Peer (P2P)
A less common approach is to configure your Spring Session application as a peer member in the Tanzu GemFire cluster using the
Peer-To-Peer (P2P) topology. In this configuration, the Spring Session application would be an actual server or data node in the Tanzu GemFire cluster, and not just a cache client as before.One advantage to this approach is the proximity of the application to the application’s state, and in particular the HttpSession
state. However, there are other effective means of accomplishing similar data dependent computations, such as using the Tanzu GemFire Function Execution. When Tanzu GemFire is serving as a provider in Spring Session, any Tanzu GemFire features can be used.
You can use the P2P topology for testing purposes and for smaller, more focused, and self-contained applications, such as those found in a microservices architecture. This can improve on your application’s perceived latency and throughput needs.
You can configure a Peer-To-Peer (P2P) topology with either of the following:
- Java-based Configuration
- XML-based Configuration
Tanzu GemFire Peer-To-Peer (P2P) Java-based Configuration
This section describes how to configure the Tanzu GemFire Peer-To-Peer (P2P) topology to manage HttpSession
state using Java-based configuration.
Spring Java Configuration
After adding the required dependencies and repository declarations, we can create the Spring configuration. The Spring configuration is responsible for creating a Servlet Filter
that replaces the javax.servlet.http.HttpSession
with an implementation backed by Spring Session and Tanzu GemFire.
Add the following Spring configuration:
@PeerCacheApplication(name = "SpringSessionDataGemFireJavaConfigP2pSample", logLevel = "error") //SEE COMMENT 1
@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = 30) //SEE COMMENT 2
public class Config {
}
-
We use the
@PeerCacheApplication
annotation to simplify the creation of a peer cache instance. -
We annotate the
Config
class with@EnableGemFireHttpSession
to create the necessary server-sideRegion
used to storeHttpSession
state. By default, thisRegion
isClusteredSpringSessions
.
The @EnableGemFireHttpSession
annotation enables developers to configure certain aspects of both Spring Session and Tanzu GemFire out-of-the-box using the following attributes:
-
clientRegionShortcut
: Specifies Tanzu GemFire
ClientRegionShortcut
(see VMware GemFire Java API Reference). Default: PROXY
. This attribute is only used when configuring the client Region
.
indexableSessionAttributes
: Identifies the Session attributes by name that should be indexed for querying purposes. Only Session attributes explicitly identified by name will be indexed.
maxInactiveIntervalInSeconds
: Controls HttpSession idle-timeout expiration. Defaults to 30 minutes.
poolName
: Name of the dedicated Tanzu GemFire Pool
used to connect a client to the cluster of servers. This attribute is only used when the application is a cache client. Default: gemfirePool
.
regionName
: Specifies the name of the Tanzu GemFire Region
used to store and manage HttpSession
state. Default: ClusteredSpringSessions
.
serverRegionShortcut
: Specifies Tanzu GemFire data management policy on the server with the RegionShortcut
(see VMware GemFire Java API Reference). Default: PARTITION
. This attribute is only used when configuring server Regions
, or when a P2P topology is employed.
Java Servlet Container Initialization
Spring Java Configuration created a Spring bean named springSessionRepositoryFilter
that implements javax.servlet.Filter
. The springSessionRepositoryFilter
bean is responsible for replacing the javax.servlet.http.HttpSession
with a custom implementation backed by Spring Session and Tanzu GemFire.
The Filter
requires Spring to load the Config
class. We must also ensure that the Servlet container, Tomcat, uses springSessionRepositoryFilter
for every HTTP request.
Spring Session provides a utility class named AbstractHttpSessionApplicationInitializer
that provides this functionality.
Example from src/main/java/sample/Initializer.java
:
public class Initializer extends AbstractHttpSessionApplicationInitializer { //SEE COMMENT 1
public Initializer() {
super(Config.class); //SEE COMMENT 2
}
}
Comments:
-
Extends
AbstractHttpSessionApplicationInitializer
to ensure that a Spring bean namedspringSessionRepositoryFilter
is registered with the Servlet container and is used for every HTTP request. -
AbstractHttpSessionApplicationInitializer
provides a mechanism to allow Spring to load theConfig
class.
Tanzu GemFire Peer-To-Peer (P2P) XML-based Configuration
This section describes how to configure the Tanzu GemFire Peer-To-Peer (P2P) topology to manage HttpSession
state using XML-based configuration.
Spring XML Configuration
After adding the required dependencies and repository declarations, we can create the Spring configuration. The Spring configuration is responsible for creating a Servlet
Filter
that replaces the javax.servlet.http.HttpSession
with an implementation backed by Spring Session and Tanzu GemFire.
Add the following Spring configuration in src/main/webapp/WEB-INF/spring/session.xml
:
<context:annotation-config/>
<context:property-placeholder/>
<!--SEE COMMENT 1-->
<util:properties id="gemfireProperties">
<prop key="name">SpringSessionDataGemFireXmlP2pSample</prop>
<prop key="log-level">${spring.data.gemfire.cache.log-level:error}</prop>
</util:properties>
<!--SEE COMMENT 2-->
<gfe:cache properties-ref="gemfireProperties"/>
<!--SEE COMMENT 3-->
<bean class="org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration"
p:maxInactiveIntervalInSeconds="30"/>
Comments:
-
(Optional) Include a
Properties
bean to configure certain aspects of the Tanzu GemFireClientCache
using GemFire Properties. In this case, we are setting VMware GemFire’slog-level
using an application-specific System property, defaulting towarning
if unspecified. -
Create a Tanzu GemFire peer
Cache
instance. Initialize it with thegemfireProperties
. -
Enable Spring Session functionality by registering an instance of
GemFireHttpSessionConfiguration
.
XML Servlet Container Initialization
Spring XML Configuration created a Spring bean named springSessionRepositoryFilter
that implements the javax.servlet.Filter
interface. The springSessionRepositoryFilter
bean is responsible for replacing the javax.servlet.http.HttpSession
with a custom implementation that is provided by Spring Session and Tanzu GemFire.
The Filter
requires that we instruct Spring to load the session.xml
configuration file. We do this with the following configuration in src/main/webapp/WEB-INF/web.xml
:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/*.xml
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
The ContextLoaderListener reads the contextConfigLocation
context parameter value and picks up the session.xml
configuration file. We must also ensure that the Servlet container, Tomcat, uses the springSessionRepositoryFilter
for every request.
The following in src/main/webapp/WEB-INF/web.xml
performs this last step:
<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>
The DelegatingFilterProxy will look up a bean by the name of springSessionRepositoryFilter
and cast it to a Filter
. For every HTTP request, the DelegatingFilterProxy
is invoked, which delegates to the springSessionRepositoryFilter
.
Configuring HttpSession
Management using Tanzu GemFire with Properties
While the @EnableGemFireHttpSession
annotation is easy to use and convenient when getting started with Spring Session and Tanzu GemFire in your Spring Boot applications, you quickly run into limitations when migrating from one environment to another, for example, like when moving from DEV to QA to PROD.
With the @EnableGemFireHttpSession
annotation attributes, it is not possible to vary the configuration from one environment to another. Therefore, Spring Session for Tanzu GemFire introduces well-known, documented properties for all the @EnableGemFireHttpSession
annotation attributes.
Property | Annotation attribute | Description | Default |
---|---|---|---|
spring.session.data.gemfire.cache.client.pool.name | EnableGemFireHttpSession.poolName | Name of the dedicated Pool used by the client Region storing/accessing Session state. | gemfirePool |
spring.session.data.gemfire.cache.client.region.shortcut | EnableGemFireHttpSession.clientRegionShortcut | Sets the client Region data management policy in the client-server topology. | ClientRegionShortcut.PROXY |
spring.session.data.gemfire.cache.server.region.shortcut | EnableGemFireHttpSession.serverRegionShortcut | Sets the peer Region data management policy in the peer-to-peer (P2P) topology. | RegionShortcut.PARTITION |
spring.session.data.gemfire.session.attributes.indexable | EnableGemFireHttpSession.indexableSessionAttributes | Comma-delimited list of Session attributes to indexed in the Session Region. | |
spring.session.data.gemfire.session.expiration.bean-name | EnableGemFireHttpSession.sessionExpirationPolicyBeanName | Name of the bean in the Spring container implementing the expiration strategy | |
spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds | EnableGemFireHttpSession.maxInactiveIntervalInSeconds | Session expiration timeout in seconds | 1800 |
spring.session.data.gemfire.session.region.name | EnableGemFireHttpSession.regionName | Name of the client or peer Region used to store and access Session state. | ClusteredSpringSessions |
spring.session.data.gemfire.session.serializer.bean-name | EnableGemFireHttpSession.sessionSerializerBeanName | Name of the bean in the Spring container implementing the serialization strategy | SessionPdxSerializer |
Table 4. Well-known, documented properties for the @EnableGemFireHttpSession
annotation attributes.
You can adjust the configuration of Spring Session when using Tanzu GemFire as your provider by using properties, as follows:
@SpringBootApplication
@ClientCacheApplication
@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = 900)
class MySpringSessionApplication {
// ...
}
And then, in application.properties
:
#application.properties
spring.session.data.gemfire.cache.client.region.shortcut=CACHING_PROXY
spring.session.data.gemfire.session.expiration.max-inactive-internval-seconds=3600
Any properties explicitly defined override the corresponding @EnableGemFireHttpSession
annotation attribute.
In the example above, even though the EnableGemFireHttpSession
annotation maxInactiveIntervalInSeconds
attribute was set to 900
seconds, or 15 minutes, the corresponding attribute property (spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds
) overrides the value and sets the expiration to 3600
seconds, or 60 minutes.
Note Properties override the annotation attribute values at runtime.
Properties of Properties
You can configure your properties with other properties, as follows:
#application.properties
spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds=${app.geode.region.expiration.timeout:3600}
Additionally, you can use Spring profiles to vary the expiration timeout or other properties based on environment or your application, or whatever criteria your application requirements dictate.
Property placeholders and nesting is a feature of the core Spring Framework and not specific to Spring Session or Spring Session for Tanzu GemFire.
Configuring HttpSession
Management using Tanzu GemFire with a Configurer
In addition to properties, Spring Session for Tanzu GemFire also allows you to adjust the configuration of Spring Session with Tanzu GemFire using the SpringSessionGemFireConfigurer
interface. The interface defines a contract containing default methods for each @EnableGemFireHttpSession
annotation attribute that can be overridden to adjust the configuration.
The SpringSessionGemFireConfigurer
is similar in concept to Spring Web MVC’s Configurer interfaces (e.g. o.s.web.servlet.config.annotation.WebMvcConfigurer
), which adjusts various aspects of your Web application’s configuration on startup, such as configuring async support. The advantage of declaring and implementing a Configurer
is that it gives you programmatic control over your configuration. You can use this in situations where you must express complex, conditional logic that determines whether the configuration should be applied or not.
For example, to adjust the client Region data management policy and Session expiration timeout, use the following:
@Configuration
class MySpringSessionConfiguration {
@Bean
SpringSessionGemFireConfigurer exampleSpringSessionGemFireConfigurer() {
return new SpringSessionGemFireConfigurer() {
@Override
public ClientRegionShortcut getClientRegionShortcut() {
return ClientRegionShortcut.CACHING_PROXY;
}
@Override
public int getMaxInactiveIntervalInSeconds() {
return 3600;
}
};
}
}
You can be as sophisticated as you like, such as by implementing your Configurer
in terms of other properties using Spring’s @Value
annotation, as follows:
@Configuration
class MySpringSessionConfiguration {
@Bean
@Primary
@Profile("production")
SpringSessionGemFireConfigurer exampleSpringSessionGemFireConfigurer(
@Value("${app.geode.region.data-management-policy:CACHING_PROXY}") ClientRegionShortcut shortcut,
@Value("${app.geode.region.expiration.timeout:3600}") int timeout) {
return new SpringSessionGemFireConfigurer() {
@Override
public ClientRegionShortcut getClientRegionShortcut() {
return shortcut;
}
@Override
public int getMaxInactiveIntervalInSeconds() {
return timeout;
}
};
}
}
Spring Boot will resolves @Value
annotation property placeholder values or SpEL Expressions automatically. However, if you are not using Spring Boot, then you must explicitly register a static PropertySourcesPlaceholderConfigurer
bean definition.
However, you can only declare one SpringSessionGemFireConfigurer
bean in the Spring container at a time, unless you are also using Spring profiles or have marked one of the multiple SpringSessionGemFireConfigurer
beans as primary by using Spring’s @Primary
context annotation.
Configuration Precedence
A SpringSessionGemFireConfigurer
takes precedence over either the @EnableGemFireHttpSession
annotation attributes or any of the commonly-known and documented Spring Session for Tanzu GemFire properties defined in Spring Boot application.properties.
If more than one configuration approach is employed by your Web application, the following precedence will apply:
-
SpringSessionGemFireConfigurer
“implemented” callback methods -
Documented Spring Session for Tanzu GemFire properties. See corresponding
@EnableGemFireHttpSession
annotation attribute Javadoc. For example,spring.session.data.gemfire.session.region.name
. -
@EnableGemFireHttpSession
annotation attributes
Spring Session for Tanzu GemFire is careful to only apply configuration from a SpringSessionGemFireConfigurer
bean declared in the Spring container for the methods you have implemented.
In the example above, since the getRegionName()
method was not implemented, the name of the Tanzu GemFire Region managing the HttpSession
state will not be determined by the Configurer.
Example
Example Spring Session for Tanzu GemFire Configuration
@ClientCacheApplication
@EnableGemFireHttpSession(
maxInactiveIntervalInSeconds = 3600,
poolName = "DEFAULT"
)
class MySpringSessionConfiguration {
@Bean
SpringSessionGemFireConfigurer sessionExpirationTimeoutConfigurer() {
return new SpringSessionGemFireConfigurer() {
@Override
public int getMaxInactiveIntervalInSeconds() {
return 300;
}
};
}
}
Example Spring Boot application.properties
file:
spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds = 900
spring.session.data.gemfire.session.region.name = Sessions
The Session expiration timeout will be 300 seconds, overriding both the property spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds
of 900 seconds, as well as the explicit @EnableGemFireHttpSession.maxInactiveIntervalInSeconds
annotation attribute value of 3600 seconds.
Because the “sessionExpirationTimeoutConfigurer” bean does not override the getRegionName()
method, the Session Region name will be determined by the property (spring.session.data.gemfire.session.region.name
), set to “Sessions”, which overrides the implicit @EnableGemFireHttpSession.regionName
annotation attribute’s default value of “ClusteredSpringSessions”.
The @EnableGemFireHttpSession.poolName
annotation attribute’s value of “DEFAULT” will determine the name of the Pool used when sending Region operations between the client and server to manage Session state on the servers because neither the corresponding property (spring.session.data.gemfire.cache.client.pool.name`) was set nor was the SpringSessionGemFireConfigurer.getPoolName()
method overridden by the “sessionExpirationTimeoutConfigurer” bean.
The client Region used to manage Session state will have a data management policy of PROXY
, the default value for the @EnableGemFireHttpSession.clientRegionShortcut
annotation attribute, which was not explicitly set, nor was the corresponding property (spring.session.data.gemfire.cache.client.region.shortcut
) for this attribute. And, because the SpringSessionConfigurer.getClientRegionShortcut()
method was not overridden, the default value is used.
Tanzu GemFire Expiration
By default, Tanzu GemFire is configured with a Region Entry, Idle Timeout (TTI) Expiration Policy, using an expiration timeout of 30 minutes and “INVALIDATE” entry as the action. When a user’s Session remains inactive or idle for more than 30 minutes, the Session will expire and is invalidated, and the user must begin a new Session to continue to use the application.
If you have application specific requirements around Session state management and expiration, and using the default, Idle Timeout (TTI) Expiration Policy is insufficient for your Use Case, Spring Session for Tanzu GemFire supports application-specific, custom expiration policies. As an application developer, you can specify custom rules governing the expiration of a Session managed by Spring Session, backed by Tanzu GemFire. Spring Session for Tanzu GemFire provides this SessionExpirationPolicy
Strategy interface.
SessionExpirationPolicy Interface
@FunctionalInterface
interface SessionExpirationPolicy {
// determine timeout for expiration of individual Session
Optional<Duration> determineExpirationTimeout(Session session);
// define the action taken on expiration
default ExpirationAction getExpirationAction() {
return ExpirationAction.INVALIDATE;
}
enum ExpirationAction {
DESTROY,
INVALIDATE
}
}
You implement this interface to specify the Session expiration policies required by your application and then register the instance as a bean in the Spring application context.
Use the @EnableGemFireHttpSession
annotation, sessionExpirationPolicyBeanName
attribute to configure the name of the SessionExpirationPolicy
bean implementing your custom application policies and rules for Session expiration.
For example:
Custom SessionExpirationPolicy
class MySessionExpirationPolicy implements SessionExpirationPolicy {
public Optional<Duration> determineExpirationTimeout(Session session) {
// return a java.time.Duration specifying the length of time until the Session should expire
}
}
Then declare the following custom SessionExpirationPolicy
configuration in your application class:
@SpringBootApplication
@EnableGemFireHttpSession(
maxInactiveIntervalInSeconds = 600,
sessionExpirationPolicyBeanName = "expirationPolicy"
)
class MySpringSessionApplication {
@Bean
SessionExpirationPolicy expirationPolicy() {
return new MySessionExpirationPolicy();
}
}
The name of the SessionExpirationPolicy
bean can be configured using the spring.session.data.gemfire.session.expiration.bean-name
property, or by declaring a SpringSessionGemFireConfigurer
bean in the Spring container and overriding the getSessionExpirationPolicyBeanName()
method.
You are only required to implement the determineExpirationTimeout(:Session):Optional<Duration>
method, which encapsulates the rules to determine when the Session should expire. The expiration timeout for a Session is expressed as an Optional
of java.time.Duration
, which specifies the length of time until the Session expires.
The determineExpirationTimeout
method can be Session-specific and can change with each invocation.
Optionally, you can implement the getAction
method to specify the action taken when the Session expires. By default, the Region Entry (i.e. Session) is invalidated. Another option is to destroy the Region Entry on expiration, which removes both the key (Session ID) and value (Session). Invalidate only removes the value.
The SessionExpirationPolicy
is adapted into an instance of the CustomExpiry
(see VMware GemFire Java API Reference) interface. This Spring Session CustomExpiry
object is then set as the Session Region’s custom entry idle timeout expiration policy
(see VMware GemFire Java API Reference).
During expiration determination, the CustomExpiry.getExpiry(:Region.Entry<String, Session>):ExpirationAttributes
method is invoked for each entry (i.e. Session) in the Region every time the expiration threads run, which in turn calls the SessionExpirationPolicy.determineExpirationTimout(:Session):Optional<Duration>
method. The returned java.time.Duration
is converted to seconds and used as the expiration timeout in the ExpirationAttributes
(see VMware GemFire Java API Reference) returned from the CustomExpiry.getExpiry(..)
method invocation.
The Tanzu GemFire expiration threads run once every second, evaluating each entry (i.e. Session) in the Region to determine if the entry has expired. You can control the number of expiration threads with the gemfire.EXPIRY_THREADS
property.
Expiration Timeout Configuration
To base the expiration timeout for your custom SessionExpirationPolicy
on the @EnableGemFireHttpSession
annotation, maxInactiveIntervalInSeconds
attribute, or alternatively, the corresponding spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds
property, then your custom SessionExpirationPolicy
implementation may also implement the SessionExpirationTimeoutAware
interface.
The SessionExpirationTimeoutAware
interface is defined as follows:
interface SessionExpirationTimeoutAware {
void setExpirationTimeout(Duration expirationTimeout);
}
When your custom SessionExpirationPolicy
implementation also implements the SessionExpirationTimeoutAware
interface, then Spring Session for VMware Tanzu GemFire will supply your implementation with the value from the @EnableGemFireHttpSession
annotation, maxInactiveIntervalInSeconds
attribute, or from the spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds
property if set, or from any SpringSessionGemFireConfigurer
bean declared in the Spring application context, as an instance of java.time.Duration
.
If more than one configuration option is used, the following order takes precedence:
-
SpringSessionGemFireConfigurer.getMaxInactiveIntervalInSeconds()
-
spring.session.data.gemfire.session.expiration.max-inactive-interval-seconds
property -
@EnableGemFireHttpSession
annotation,maxInactiveIntervalInSeconds
attribute
Fixed Timeout Expiration
Spring Session for Tanzu GemFire provides an implementation of the SessionExpirationPolicy
interface for fixed duration expiration, also known as Absolute session timeouts.
It is perhaps necessary, in certain cases, such as for security reasons, to expire the user’s Session after a fixed length of time (e.g. every hour), regardless if the user’s Session is still active.
Spring Session for Tanzu GemFire provides the FixedTimeoutSessionExpirationPolicy
implementation out-of-the-box for this exact Use Case (UC). In addition to handling fixed duration expiration, it is also careful to still consider and apply the default, idle expiration timeout.
For instance, consider a scenario where a user logs in, beginning a Session, is active for 10 minutes and then leaves letting the Session sit idle. If the fixed duration expiration timeout is set for 60 minutes, but the idle expiration timeout is only set for 30 minutes, and the user does not return, then the Session should expire in 40 minutes and not 60 minutes when the fixed duration expiration would occur.
Conversely, if the user is busy for a full 40 minutes, thereby keeping the Session active, thus avoiding the 30-minute idle expiration timeout, and then leaves, then our fixed duration expiration timeout should kick in and expire the user’s Session right at 60 minutes, even though the user’s idle expiration timeout would not occur until 70 minutes in (40 min (active) + 30 min (idle) = 70 minutes).
Well, this is exactly what the FixedTimeoutSessionExpirationPolicy
does.
To configure the FixedTimeoutSessionExpirationPolicy
:
Fixed Duration Expiration Configuration
@SpringBootApplication
@EnableGemFireHttpSession(sessionExpirationPolicyBeanName = "fixedTimeoutExpirationPolicy")
class MySpringSessionApplication {
@Bean
SessionExpirationPolicy fixedTimeoutExpirationPolicy() {
return new FixedTimeoutSessionExpirationPolicy(Duration.ofMinutes(60L));
}
}
In the example above, the FixedTimeoutSessionExpirationPolicy
was declared as a bean in the Spring application context and initialized with a fixed duration expiration timeout of 60 minutes. As a result, the users Session will either expire after the idle timeout (which defaults to 30 minutes) or after the fixed timeout (configured to 60 minutes), which ever occurs first.
You can also implement lazy, fixed duration expiration timeout on Session access by using the Spring Session for VMware Tanzu GemFire FixedDurationExpirationSessionRepositoryBeanPostProcessor
. This BPP wraps any data store specific SessionRepository
in a FixedDurationExpirationSessionRepository
implementation that evaluates a Sessions expiration on access, only. This approach is agnostic to the underlying data store and therefore can be used with any Spring Session provider. The expiration determination is based solely on the Session creationTime
property and the required java.time.Duration
specifying the fixed duration expiration timeout.
Note: The
FixedDurationExpirationSessionRepository
should not be used
in strict expiration timeout cases, such as when the Session must expire
immediately after the fixed duration expiration timeout has elapsed.
Additionally, unlike the
FixedTimeoutSessionExpirationPolicy
, the
FixedDurationExpirationSessionRepository does not take idle
expiration timeout into consideration. It only uses the fixed
duration when determining the expiration timeout for a given
Session.
SessionExpirationPolicy
Chaining
Using the Composite software design pattern, you can treat a group of SessionExpirationPolicy
instances as a single instance, functioning as if in a chain much like the chain of Servlet Filters themselves.
The Composite software design pattern is a powerful pattern and is supported by the SessionExpirationPolicy
, @FunctionalInterface
, simply by returning an Optional
of java.time.Duration
from the determineExpirationTimeout
method.
This allows each composed SessionExpirationPolicy
to “optionally” return a Duration
only if the expiration could be determined by this instance. Alternatively, this instance may punt to the next SessionExpirationPolicy
in the composition, or chain until either a non-empty expiration timeout is returned, or ultimately no expiration timeout is returned.
In fact, this very policy is used internally by the FixedTimeoutSessionExpirationPolicy
, which will return Optional.empty()
in the case where the idle timeout will occur before the fixed timeout. By returning no expiration timeout, Tanzu GemFire will defer to the default, configured entry idle timeout expiration policy on the Region managing Session state.
Tanzu GemFire Serialization
To transfer data between clients and servers, or when data is distributed and replicated between peer nodes in a cluster, the data must be serialized. In this case, the data in question is the Session’s state.
Anytime a Session is persisted or accessed in a Client-Server topology, the Session’s state is communicated. Typically, a Spring Boot application with Spring Session enabled will be a client to the servers in a cluster of Tanzu GemFire nodes.
On the server-side, Session state maybe distributed across several servers (data nodes) in the cluster to replicate the data and guarantee a high availability of the Session state. When using Tanzu GemFire, data can be partitioned, or sharded, and a redundancy-level can be specified. When the data is distributed for replication, it must also be serialized to transfer the Session state among the peer nodes in the cluster.
Tanzu GemFire supports Java Serialization. There are many advantages to Java Serialization, such as handling cycles in the object graph, or being universally supported by any application written in Java. However, Java Serialization is very verbose and is not the most efficient format.
As such, Tanzu GemFire provides its own serialization frameworks to serialize Java types:
Serialization with Spring Session
Previously, Spring Session for Tanzu GemFire only supported Tanzu GemFire Data Serialization format. The main motivation behind this was to take advantage of the Tanzu GemFire Delta Propagation functionality since a Session’s state can be arbitrarily large.
However, as of Spring Session for Tanzu GemFire 2.0, PDX is also supported and is now the new, default serialization option. The default was changed to PDX in Spring Session for VMware Tanzu GemFire 2.0 primarily because PDX is the most widely used and requested format by users.
PDX is certainly the most flexible format, so much so that you do not even need Spring Session for Tanzu GemFire or any of its transitive dependencies on the classpath of the servers in the cluster to use Spring Session with Tanzu GemFire. In fact, with PDX, you do not even need to put your application domain object types stored in the (HTTP) Session on the servers’ classpath either.
Essentially, when using PDX serialization, Tanzu GemFire does not require the associated Java types to be present on the servers’ classpath. So long as no deserialization happens on the servers in the cluster, you are safe.
The @EnableGemFireHttpSession
annotation introduces the new sessionSerializerBeanName
attribute that a user can use to configure the name of a bean declared and registered in the Spring container implementing the desired serialization strategy. The serialization strategy is used by Spring Session for Tanzu GemFire to serialize the Session state.
Out-of-the-box, Spring Session for Tanzu GemFire provides two serialization strategies: one for PDX and one for Data Serialization. It automatically registers both serialization strategy beans in the Spring container. However, only one of those strategies is actually used at runtime, PDX!
The two beans registered in the Spring container implementing Data Serialization and PDX are named SessionDataSerializer
and SessionPdxSerializer
, respectively. By default, the sessionSerializerBeanName
attribute is set to SessionPdxSerializer
, as if the user annotated his/her Spring Boot, Spring Session enabled application configuration class with:
@SpringBootApplication
@EnableGemFireHttpSession(sessionSerializerBeanName = "SessionPdxSerializer")
class MySpringSessionApplication {
}
It is a simple matter to change the serialization strategy to Data Serialization instead by setting the sessionSerializerBeanName
attribute to SessionDataSerializer
, as follows:
@SpringBootApplication
@EnableGemFireHttpSession(sessionSerializerBeanName = "SessionDataSerializer")
class MySpringSessionApplication {
}
Since these two values are so common, Spring Session for VMware Tanzu GemFire provides constants for each value in the GemFireHttpSessionConfiguration
class: GemFireHttpSessionConfiguration.SESSION_PDX_SERIALIZER_BEAN_NAME
and GemFireHttpSessionConfiguration.SESSION_DATA_SERIALIZER_BEAN_NAME
. So, you could explicitly configure PDX, as follows:
import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration;
@SpringBootApplication
@EnableGemFireHttpSession(sessionSerializerBeanName = GemFireHttpSessionConfiguration.SESSION_PDX_SERIALIZER_BEAN_NAME)
class MySpringSessionApplication {
}
With one attribute and two provided bean definitions out-of-the-box, you can specify which Serialization framework you wish to use with your Spring Boot, Spring Session enabled application backed by Tanzu GemFire.
Spring Session for Tanzu GemFire Serialization Framework
To abstract away the details of the Tanzu GemFire Data Serialization and PDX Serialization frameworks, Spring Session for VMware Tanzu GemFire provides its own Serialization framework (facade) wrapping the Tanzu GemFire Serialization frameworks.
The Serialization API exists under the org.springframework.session.data.gemfire.serialization
package. The primary interface in this API is the org.springframework.session.data.gemfire.serialization.SessionSerializer
.
The Spring Session SessionSerializer
interface is defined as follows:
interface SessionSerializer<T, IN, OUT> {
void serialize(T session, OUT out);
T deserialize(IN in);
boolean canSerialize(Class<?> type);
// calls Object.getClass() in a null-safe way and then calls and returns canSerialize(:Class)
boolean canSerialize(Object obj);
}
The interface allows you to serialize and deserialize a Spring Session
object.
The IN
and OUT
type parameters and corresponding method arguments of those types provide reference to the objects responsible for writing the Session
to a stream of bytes or reading the Session
from a stream of bytes. The actual arguments will be type specific, based on the underlying Tanzu GemFire Serialization strategy configured.
For instance, when using the Tanzu GemFire PDX Serialization framework, IN
and OUT
will be instances of org.apache.geode.pdx.PdxReader
and org.apache.geode.pdx.PdxWriter
, respectively. When the Tanzu GemFire Data Serialization framework has been configured, then IN
and OUT
will be instances of java.io.DataInput
and java.io.DataOutput
, respectively.
These arguments are provided to the SessionSerializer
implementation by the framework automatically, and as previously mentioned, is based on the underlying Tanzu GemFire Serialization strategy configured.
Essentially, even though Spring Session for Tanzu GemFire provides a facade around the Tanzu GemFire Serialization frameworks, under-the-hood Tanzu GemFire still expects one of these Serialization frameworks is being used to serialize data to/from Tanzu GemFire.
So what purpose does the SessionSerializer
interface really serve then?
Effectively, it allows a user to customize what aspects of the Session’s state actually gets serialized and stored in Tanzu GemFire. Application developers can provide their own custom, application-specific SessionSerializer
implementation, register it as a bean in the Spring container, and then configure it to be used by Spring Session for Tanzu GemFire to serialize the Session state, as follows:
@EnableGemFireHttpSession(sessionSerializerBeanName = "MyCustomSessionSerializer")
class MySpringSessionDataGemFireApplication {
@Bean("MyCustomSessionSerializer")
SessionSerializer<Session, ?, ?> myCustomSessionSerializer() {
// ...
}
}
Implementing a SessionSerializer
Spring Session for Tanzu GemFire provides assistance when a user wants to implement a custom SessionSerializer
that fits into one of the Tanzu GemFire Serialization frameworks.
If the user just implements the org.springframework.session.data.gemfire.serialization.SessionSerializer
interface directly without extending from one of the Spring Session for VMware Tanzu GemFire provided abstract base classes, related to one of the Tanzu GemFire Serialization frameworks , then Spring Session for VMware Tanzu GemFire will wrap the user’s custom SessionSerializer
implementation in an instance of org.springframework.session.data.gemfire.serialization.pdx.support.PdxSerializerSessionSerializerAdapter
and register it with Tanzu GemFire as a org.apache.geode.pdx.PdxSerializer
.
Spring Session for Tanzu GemFire is careful not to stomp on any existing PdxSerializer
implementation that a user may have already registered with Tanzu GemFire by some other means. Several different, provided implementations of the Tanzu GemFire org.apache.geode.pdx.PdxSerializer
interface exists:
-
Tanzu GemFire provides the
org.apache.geode.pdx.ReflectionBasedAutoSerializer
(see VMware GemFire Java API Reference). -
Spring Data for VMware Tanzu GemFire (SDG) provides the org.springframework.data.gemfire.mapping.MappingPdxSerializer, which is used in the SD Repository abstraction and SDG’s extension to handle mapping PDX serialized types to the application domain object types defined in the application Repository interfaces.
This is accomplished by obtaining any currently registered PdxSerializer
instance on the cache and composing it with the PdxSerializerSessionSerializerAdapter
wrapping the user’s custom application SessionSerializer
implementation and re-registering this “composite” PdxSerializer
on the Tanzu GemFire cache. The “composite” PdxSerializer
implementation is provided by the Spring Session for VMware Tanzu GemFire org.springframework.session.data.gemfire.pdx.support.ComposablePdxSerializer
class when entities are stored in Tanzu GemFire as PDX.
If no other PdxSerializer
was currently registered with the Tanzu GemFire cache, then the adapter is simply registered.
Of course, you are allowed to force the underlying Tanzu GemFire Serialization strategy used with a custom SessionSerializer
implementation by doing one of the following:
-
The custom
SessionSerializer
implementation can implement the Tanzu GemFireorg.apache.geode.pdx.PdxSerializer
interface, or for convenience, extend Spring Session for VMware GemFire’sorg.springframework.session.data.gemfire.serialization.pdx.AbstractPdxSerializableSessionSerializer
class and Spring Session for Tanzu GemFire will register the customSessionSerializer
as aPdxSerializer
with VMware GemFire. -
The custom
SessionSerializer
implementation can extend the Tanzu GemFireorg.apache.geode.DataSerializer
class, or for convenience, extend Spring Session for VMware GemFire’sorg.springframework.session.data.gemfire.serialization.data.AbstractDataSerializableSessionSerializer
class and Spring Session for Tanzu GemFire will register the customSessionSerializer
as aDataSerializer
with Tanzu GemFire. -
A user can create a custom
SessionSerializer
implementation as before, not specifying which Tanzu GemFire Serialization framework to use because the customSessionSerializer
implementation does not implement any VMware GemFire serialization interfaces or extend from any of Spring Session for Tanzu GemFire provided abstract base classes, and still have it registered in Tanzu GemFire as aDataSerializer
by declaring an additional Spring Session for Tanzu GemFire bean in the Spring container of typeorg.springframework.session.data.gemfire.serialization.data.support.DataSerializerSessionSerializerAdapter
, like so…
Forcing the registration of a custom SessionSerializer as a DataSerializer in Tanzu GemFire
@EnableGemFireHttpSession(sessionSerializerBeanName = "customSessionSerializer")
class Application {
@Bean
DataSerializerSessionSerializerAdapter<?> dataSerializerSessionSerializer() {
return new DataSerializerSessionSerializerAdapter<>();
}
@Bean
SessionSerializer<Session, ?, ?> customSessionSerializer() {
// ...
}
}
Just by the very presence of the DataSerializerSessionSerializerAdapter
registered as a bean in the Spring container, any neutral custom SessionSerializer
implementation will be treated and registered as a DataSerializer
in Tanzu GemFire.
Additional Support for Data Serialization
When using the Tanzu GemFire DataSerialization framework, especially from the client when serializing (HTTP) Session state to the servers in the cluster, you must take care to configure the Tanzu GemFire servers in your cluster with the appropriate dependencies. This is especially true when leveraging deltas as explained in the earlier section on Data Serialization.
When using the DataSerialization framework as your serialization strategy to serialize (HTTP) Session state from your Web application clients to the servers, then the servers must be properly configured with the Spring Session for Tanzu GemFire class types used to represent the (HTTP) Session and its contents. This means including the Spring JARs on the servers classpath.
Additionally, using DataSerialization may also require you to include the JARs containing your application domain classes that are used by your Web application and put into the (HTTP) Session as Session Attribute values, particularly if:
-
Your types implement the
org.apache.geode.DataSerializable
interface. -
Your types implement the
org.apache.geode.Delta
interface. -
You have registered a
org.apache.geode.DataSerializer
that identifies and serializes the types. -
Your types implement the
java.io.Serializable
interface.
You must ensure your application domain object types put in the (HTTP) Session are serializable in some form or another. However, you are not strictly required to use DataSerialization nor are you necessarily required to have your application domain object types on the servers classpath if:
-
Your types implement the
org.apache.geode.pdx.PdxSerializable
interface. -
You have registered an
org.apache.geode.pdx.PdxSerializer
that properly identifies and serializes your application domain object types.
Tanzu GemFire will apply the following order of precedence when determining the serialization strategy to use to serialize an object graph:
-
DataSerializable
objects and any registeredDataSerializers
identifying the objects to serialize. -
PdxSerializable
objects and any registeredPdxSerializer
identifying the objects to serialize. -
All
java.io.Serializable
types.
This also means that if a particular application domain object type (e.g. A
) implements java.io.Serializable
, however, a (custom) PdxSerializer
has been registered with Tanzu GemFire identifying the same application domain object type (i.e. A
), then Tanzu GemFire will use PDX to serialize “A” and not Java Serialization, in this case.
This is especially useful since then you can use DataSerialization to serialize the (HTTP) Session object, leveraging Deltas and all the powerful features of DataSerialization, but then use PDX to serialize your application domain object types, which greatly simplifies the configuration and/or effort involved.
Now that we have a general understanding of why this support exists, how do you enable it?
Configuration
-
Create a Tanzu GemFire
cache.xml
, as follows:<?xml version="1.0" encoding="UTF-8"?> <cache xmlns="http://geode.apache.org/schema/cache" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://geode.apache.org/schema/cache https://geode.apache.org/schema/cache/cache-1.0.xsd" version="1.0"> <initializer> <class-name> org.springframework.session.data.gemfire.serialization.data.support.DataSerializableSessionSerializerInitializer </class-name> </initializer> </cache>
-
Start your servers in
gfsh
using:gfsh> start server --name=InitializedServer --cache-xml-file=/path/to/cache.xml --classpath=...
Configuring the Tanzu GemFire server classpath
with the appropriate dependencies is the tricky part, but generally, the following CLASSPATH configuration should work:
gfsh> set variable --name=REPO_HOME --value=${USER_HOME}/.m2/repository
gfsh> start server ... --classpath=\
${REPO_HOME}/org/springframework/spring-core/{spring-version}/spring-core-{spring-version}.jar\
:${REPO_HOME}/org/springframework/spring-aop/{spring-version}/spring-aop-{spring-version}.jar\
:${REPO_HOME}/org/springframework/spring-beans/{spring-version}/spring-beans-{spring-version}.jar\
:${REPO_HOME}/org/springframework/spring-context/{spring-version}/spring-context-{spring-version}.jar\
:${REPO_HOME}/org/springframework/spring-context-support/{spring-version}/spring-context-support-{spring-version}.jar\
:${REPO_HOME}/org/springframework/spring-expression/{spring-version}/spring-expression-{spring-version}.jar\
:${REPO_HOME}/org/springframework/spring-jcl/{spring-version}/spring-jcl-{spring-version}.jar\
:${REPO_HOME}/org/springframework/spring-tx/{spring-version}/spring-tx-{spring-version}.jar\
:${REPO_HOME}/org/springframework/data/spring-data-commons/{spring-data-commons-version}/spring-data-commons-{spring-data-commons-version}.jar\
:${REPO_HOME}/org/springframework/data/spring-data-geode/{spring-data-geode-version}/spring-data-geode-{spring-data-geode-version}.jar\
:${REPO_HOME}/org/springframework/session/spring-session-core/{spring-session-core-version}/spring-session-core-{spring-session-core-version}.jar\
:${REPO_HOME}/org/springframework/session/spring-session-3.0-gemfire-10.0/ 1.0.0/spring-session-3.0-gemfire-10.0- 1.0.0.jar\
:${REPO_HOME}/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar
You may need to add your application domain object JAR files to the server classpath as well.
Customizing Change Detection
By default, anytime the Session is modified (e.g. the lastAccessedTime
is updated to the current time), the Session is considered dirty by Spring Session for Tanzu GemFire (SSDG). When using Tanzu GemFire Data Serialization framework, it is extremely useful and valuable to take advantage of the Tanzu GemFire Delta Propagation capabilities as well.
When using Data Serialization, SSDG also uses Delta Propagation to send only changes to the Session state between the client and server. This includes any Session attributes that may have been added, removed or updated.
By default, anytime Session.setAttribute(name, value)
is called, the Session attribute is considered “dirty” and will be sent in the delta between the client and server. This is true even if your application domain object has not been changed.
Typically, there is never a reason to call Session.setAttribute(..)
unless your object has been changed. However, if this can occur, and your objects are relatively large (with a complex object hierarchy), then you may want to consider either:
-
Implementing the
Delta
(see VMware GemFire Java API Reference) interface in your application domain object model, while useful, is very invasive. -
Provide a custom implementation of SSDG’s
org.springframework.session.data.gemfire.support.IsDirtyPredicate
strategy interface.
Out of the box, SSDG provides five implementations of the IsDirtyPredicate
strategy interface:
Class | Description | Default |
---|---|---|
IsDirtyPredicate.ALWAYS_DIRTY | New Session attribute values are always considered dirty. | |
IsDirtyPredicate.NEVER_DIRTY | New Session attribute values are never considered dirty. | |
DeltaAwareDirtyPredicate | New Session attribute values are considered dirty when the old value and new value are different, if the new value's type does not implement `Delta` or the new value's `Delta.hasDelta()` method returns true. | Yes |
EqualsDirtyPredicate | New Session attribute values are considered dirty iff the old value is not equal to the new value as determined by `Object.equals(:Object)` method. | |
IdentityEqualsPredicate | New Session attributes values are considered dirty iff the old value is not the same as the new value using the identity equals operator (i.e. `oldValue != newValue`). |
As shown in the table above, the DeltaAwareDirtyPredicate
is the default implementation used by SSDG. The DeltaAwareDirtyPredicate
automatically takes into consideration application domain objects that implement the Tanzu GemFire Delta
interface. However, DeltaAwareDirtyPredicate
works even when your application domain objects do not implement the Delta
interface. SSDG will consider your application domain object to be dirty anytime the Session.setAttribute(name, newValue)
is called providing the new value is not the same as old value, or the new value does not implement the Delta
interface.
You can change SSDG’s dirty implementation, determination strategy simply by declaring a bean in the Spring container of the IsDirtyPredicate
interface type:
@EnableGemFireHttpSession
class ApplicationConfiguration {
@Bean
IsDirtyPredicate equalsDirtyPredicate() {
return EqualsDirtyPredicate.INSTANCE;
}
}
Composition
The IsDirtyPredicate
interface also provides the andThen(:IsDirtyPredicate)
and orThen(:IsDirtyPredicate)
methods to compose two or more IsDirtyPredicate
implementations in a composition in order to organize complex logic and rules for determining whether an application domain object is dirty or not.
For instance, you could compose both EqualsDirtyPredicate
and DeltaAwareDirtyPredicate
using the OR operator:
@EnableGemFireHttpSession
class ApplicationConfiguration {
@Bean
IsDirtyPredicate equalsOrThenDeltaDirtyPredicate() {
return EqualsDirtyPredicate.INSTANCE
.orThen(DeltaAwareDirtyPredicate.INSTANCE);
}
}
You may even implement your own, custom IsDirtyPredicates
based on specific application domain object types:
class CustomerDirtyPredicate implements IsDirtyPredicate {
public boolean isDirty(Object oldCustomer, Object newCustomer) {
if (newCustomer instanceof Customer) {
// custom logic to determine if a new Customer is dirty
}
return true;
}
}
class AccountDirtyPredicate implements IsDirtyPredicate {
public boolean isDirty(Object oldAccount, Object newAccount) {
if (newAccount instanceof Account) {
// custom logic to determine if a new Account is dirty
}
return true;
}
}
Then combine CustomerDirtyPredicate
with the AccountDirtyPredicate
and a default predicate for fallback, as follows:
Composed and configured type-specific IsDirtyPredicates
@EnableGemFireHttpSession
class ApplicationConfiguration {
@Bean
IsDirtyPredicate typeSpecificDirtyPredicate() {
return new CustomerDirtyPredicate()
.andThen(new AccountDirtyPredicate())
.andThen(IsDirtyPredicate.ALWAYS_DIRTY);
}
}
Warning: Use caution when implementing custom
IsDirtyPredicate
strategies. If you incorrectly determine
that your application domain object is not dirty when it actually is,
then it will not be sent in the Session delta from the client to the
server.
Changing the Session Representation
Internally, Spring Session for Tanzu GemFire maintains two representations of the (HTTP) Session and the Session’s attributes. Each representation is based on whether Tanzu GemFire “Deltas” are supported or not.
Tanzu GemFire Delta Propagation is only enabled by Spring Session for VMware Tanzu GemFire when using Data Serialization for reasons that were discussed in PDX Serialization Background.
The strategy is:
-
If Tanzu GemFire Data Serialization is configured, then Deltas are supported and the
DeltaCapableGemFireSession
andDeltaCapableGemFireSessionAttributes
representations are used. -
If Tanzu GemFire PDX Serialization is configured, then Delta Propagation will be disabled and the
GemFireSession
andGemFireSessionAttributes
representations are used.
It is possible to override these internal representations used by Spring Session for VMware Tanzu GemFire, and for users to provide their own Session related types. The only strict requirement is that the Session implementation must implement the core Spring Session org.springframework.session.Session
interface.
By way of example, let’s say you want to define your own Session implementation.
First, you define the Session
type. Perhaps your custom Session
type even encapsulates and handles the Session attributes without having to define a separate type.
User-defined Session interface implementation
class MySession implements org.springframework.session.Session {
// ...
}
Then, you would need to extend the org.springframework.session.data.gemfire.GemFireOperationsSessionRepository
class and override the createSession()
method to create instances of your custom Session
implementation class.
Custom SessionRepository implementation creating and returning instances of the custom Session type
class MySessionRepository extends GemFireOperationsSessionRepository {
@Override
public Session createSession() {
return new MySession();
}
}
If you provide your own custom SessionSerializer
implementation and Tanzu GemFire PDX Serialization is configured, then you are finished.
However, if you configured Tanzu GemFire Data Serialization, then you must additionally provide a custom implementation of the SessionSerializer
interface and either have it directly extend the Tanzu GemFire org.apache.geode.DataSerializer
class, or extend the Spring Session for Tanzu GemFire org.springframework.session.data.gemfire.serialization.data.AbstractDataSerializableSessionSerializer
class and override the getSupportedClasses():Class<?>[]
method.
For example:
Custom SessionSerializer for custom Session type
class MySessionSerializer extends AbstractDataSerializableSessionSerializer<MySession> {
@Override
public Class<?>[] getSupportedClasses() {
return new Class[]{MySession.class};
}
@Override
public void serialize(MySession mySession, DataOutput dataOutput) {
...
}
@Override
public MySession deserialize(DataInput dataInput) {
...
}
}
Unfortunately, getSupportedClasses()
cannot return the generic Spring Session org.springframework.session.Session
interface type. If it could then we could avoid the explicit need to override the getSupportedClasses()
method on the custom DataSerializer
implementation. But, the Tanzu GemFire Data Serialization framework can only match on exact class types since it incorrectly and internally stores and refers to the class type by name, which then requires the user to override and implement the getSupportedClasses()
method.
How HttpSession Integration Works
Fortunately, both javax.servlet.http.HttpSession
and javax.servlet.http.HttpServletRequest
(the API for obtaining an HttpSession
) are interfaces. This means we can provide our own implementations for each of these APIs.
Note: This section describes how Spring Session provides
transparent integration with javax.servlet.http.HttpSession
. The intent is to explain what is happening out of sight. This functionality is
already implemented and integrated so you do not need to implement this
logic yourself.
First, we create a custom javax.servlet.http.HttpServletRequest
that returns a custom implementation of javax.servlet.http.HttpSession
. It looks something like the following:
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 ...
}
Any method that returns an javax.servlet.http.HttpSession
is overridden. All other methods are implemented by javax.servlet.http.HttpServletRequestWrapper
and simply delegate to the original javax.servlet.http.HttpServletRequest
implementation.
We replace the javax.servlet.http.HttpServletRequest
implementation using a Servlet Filter
called SessionRepositoryFilter
. The pseudocode can be found below:
public class SessionRepositoryFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
SessionRepositoryRequestWrapper customRequest = new SessionRepositoryRequestWrapper(httpRequest);
chain.doFilter(customRequest, response);
}
// ...
}
By passing in a custom javax.servlet.http.HttpServletRequest
implementation into the FilterChain
we ensure that anything invoked after our Filter
uses the custom javax.servlet.http.HttpSession
implementation.
This highlights why it is important that Spring Session’s SessionRepositoryFilter
must be placed before anything that interacts with the javax.servlet.http.HttpSession
.
HttpSessionListener
Spring Session supports HttpSessionListener
by translating SessionCreatedEvent
and SessionDestroyedEvent
into HttpSessionEvent
by declaring SessionEventHttpSessionListenerAdapter
.
To use this support, you must:
-
Ensure your
SessionRepository
implementation supports and is configured to fireSessionCreatedEvent
and` SessionDestroyedEvent`. -
Configure
SessionEventHttpSessionListenerAdapter
as a Spring bean. -
Inject every
HttpSessionListener
into theSessionEventHttpSessionListenerAdapter
If you are using the configuration support documented in HttpSession with Tanzu GemFire, then all you need to do is register every HttpSessionListener
as a bean.
For example, assume you want to support Spring Security’s concurrency control and need to use HttpSessionEventPublisher
, then you can simply add HttpSessionEventPublisher
as a bean.
Session
A Session
is a simplified Map
of key/value pairs with support for expiration.
SessionRepository
A SessionRepository
is in charge of creating, persisting and accessing Session
instances and state.
If possible, developers should not interact directly with a SessionRepository
or a Session
. Instead, developers should prefer to interact with SessionRepository
and Session
indirectly through the javax.servlet.http.HttpSession
, WebSocket
and WebSession
integration.
FindByIndexNameSessionRepository
Spring Session’s most basic API for using a Session
is the SessionRepository
. The API is intentionally very simple so that it is easy to provide additional implementations with basic functionality.
Some SessionRepository
implementations may choose to implement FindByIndexNameSessionRepository
also. For example, Spring Session’s for Tanzu GemFire support implements FindByIndexNameSessionRepository
.
The FindByIndexNameSessionRepository
adds a single method to look up all the sessions for a particular user. This is done by ensuring that the session attribute with the name FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME
is populated with the username. It is the responsibility of the developer to ensure the attribute is populated since Spring Session is not aware of the authentication mechanism being used.
EnableSpringHttpSession
The @EnableSpringHttpSession
annotation can be added to any @Configuration
class to expose the SessionRepositoryFilter
as a bean in the Spring container named, “springSessionRepositoryFilter”.
In order to leverage the annotation, a single SessionRepository
bean must be provided.
EnableGemFireHttpSession
The @EnableGemFireHttpSession
annotation can be added to any @Configuration
class in place of the @EnableSpringHttpSession
annotation to expose the SessionRepositoryFilter
as a bean in the Spring container named, “springSessionRepositoryFilter” and to position Tanzu GemFire as a provider managing javax.servlet.http.HttpSession
state.
When using the @EnableGemFireHttpSession
annotation, additional configuration is imported out-of-the-box that also provides a Tanzu GemFire specific implementation of the SessionRepository
interface named, GemFireOperationsSessionRepository
.
GemFireOperationsSessionRepository
GemFireOperationsSessionRepository
is a SessionRepository
implementation that is implemented using the Spring Session for VMware Tanzu GemFire GemFireOperationsSessionRepository
.
In a web environment, this repository is used in conjunction with the SessionRepositoryFilter
.
This implementation supports SessionCreatedEvents
, SessionDeletedEvents
and SessionDestroyedEvents
through SessionEventHttpSessionListenerAdapter
.
Using Indexes with Tanzu GemFire
While best practices concerning the proper definition of Indexes that positively impact Tanzu GemFire performance is beyond the scope of this document, it is important to realize that Spring Session for VMware Tanzu GemFire creates and uses Indexes to query and find Sessions efficiently.
Out-of-the-box, Spring Session for Tanzu GemFire creates one Hash-typed Index on the principal name. There are two different built-in strategies for finding the principal name. The first strategy is that the value of the Session attribute with the name FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME
will be Indexed to the same index name.
For example:
String indexName=FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
session.setAttribute(indexName,username);
Map<String, Session> idToSessions=
this.sessionRepository.findByIndexNameAndIndexValue(indexName,username);
Using Indexes with Tanzu GemFire and Spring Security
Alternatively, Spring Session for Tanzu GemFire will map Spring Security’s current Authentication#getName()
to the Index FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME
.
For example, if you are using Spring Security you can find the current user’s sessions using:
SecurityContext securityContext=SecurityContextHolder.getContext();
Authentication authentication=securityContext.getAuthentication();
String indexName=FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
Map<String, Session> idToSessions=
this.sessionRepository.findByIndexNameAndIndexValue(indexName,authentication.getName());
Using Custom Indexes with Tanzu GemFire
This enables developers using the GemFireOperationsSessionRepository
programmatically to query and find all Sessions with a given principal name efficiently.
Additionally, Spring Session for Tanzu GemFire will create a Range-based Index on the implementing Session’s Map-type attributes
property (i.e. on any arbitrary Session attribute) when a developer identifies 1 or more named Session attributes that should be indexed by Tanzu GemFire.
Sessions attributes to index can be specified with the indexableSessionAttributes
attribute on the @EnableGemFireHttpSession
annotation. A developer adds this annotation to their Spring application @Configuration
class when s/he wishes to enable Spring Session’s support for HttpSession
backed by Tanzu GemFire.
String indexName="name1";
session.setAttribute(indexName,indexValue);
Map<String, Session> idToSessions=
this.sessionRepository.findByIndexNameAndIndexValue(indexName,indexValue);
Only Session attribute names identified in the @EnableGemFireHttpSession
annotation’s indexableSessionAttributes
attribute will have an Index defined. All other Session attributes will not be indexed.
However, there is one catch. Any values stored in an indexable Session attributes must implement the java.lang.Comparable<T>
interface. If those object values do not implement Comparable
, then Tanzu GemFire will throw an error on startup when the Index is defined for Regions with persistent Session data, or when an attempt is made at runtime to assign the indexable Session attribute a value that is not Comparable
and the Session is saved to Tanzu GemFire.
Any Session attribute that is not indexed may store non-Comparable
values.
Content feedback and comments