Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,16 @@

package docs.http;

import com.hazelcast.config.AttributeConfig;
import com.hazelcast.config.Config;
import com.hazelcast.config.IndexConfig;
import com.hazelcast.config.IndexType;
import com.hazelcast.config.SerializerConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.MapSession;
import com.hazelcast.spring.session.HazelcastIndexedSessionRepository;
import com.hazelcast.spring.session.HazelcastSessionSerializer;
import com.hazelcast.spring.session.PrincipalNameExtractor;
import com.hazelcast.spring.session.HazelcastSessionConfiguration;
import com.hazelcast.spring.session.config.annotation.web.http.EnableHazelcastHttpSession;

// tag::config[]
Expand All @@ -40,16 +36,10 @@ public class HazelcastHttpSessionConfig {
@Bean
public HazelcastInstance hazelcastInstance() {
Config config = new Config();
AttributeConfig attributeConfig = new AttributeConfig()
.setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
.setExtractorClassName(PrincipalNameExtractor.class.getName());
config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME) // <2>
.addAttributeConfig(attributeConfig)
HazelcastSessionConfiguration.applySerializationConfig(config); // <2>
config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME) // <3>
.addIndexConfig(
new IndexConfig(IndexType.HASH, HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE));
SerializerConfig serializerConfig = new SerializerConfig();
serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class);
config.getSerializationConfig().addSerializerConfig(serializerConfig); // <3>
return Hazelcast.newHazelcastInstance(config); // <4>
}

Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@
** xref:spring:add-caching.adoc[]
** xref:spring:hibernate.adoc[]
** xref:spring:transaction-manager.adoc[]
** xref:spring:hazelcast-spring-session.adoc[]
** xref:spring:spring-session-guide.adoc[]
*** xref:spring:spring-hazelcast-support.adoc[]
// Feast
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/modules/spring/images/HzSS-Embedded.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
317 changes: 317 additions & 0 deletions docs/modules/spring/pages/hazelcast-spring-session.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
= Configure Hazelcast Spring Session

Hazelcast Spring Session is a library that allows you to store session information from your Spring Boot application in Hazelcast's xref:data-structures:map.adoc[IMap]. This allows you to enhance your application with the benefits of Hazelcast: resilence, high availability and high performance.

== Update dependencies

If you use Maven, add the following dependencies:

[tabs]
====
Maven::
+
.pom.xml
[source,xml]
[subs="verbatim,attributes"]
----
<dependencies>
<!-- ... -->
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>{os-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>7.0.0</version>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast-spring-session</artifactId>
<version>4.0.0</version>
</dependency>
</dependencies>
----

Gradle (Kotlin)::
+
.build.gradle.kts
[source,kotlin]
[subs="verbatim,attributes"]
----
dependencies {
implementation("com.hazelcast:hazelcast:{os-version}")
implementation("org.springframework:spring-web:7.0.0")
implementation("com.hazelcast:hazelcast-spring-session:4.0.0")
}
----

====

TIP: Hazelcast Spring Session uses the same major version number as Spring Boot it was created for. For Spring Boot 4.x, the compatible Hazelcast Spring Session will be always 4.x.

=== Migrate from Spring Session Hazelcast 3.x

From version 4.0, the Hazelcast integration with Spring Session is owned by the Hazelcast team.

To migrate your application from using Spring Session Hazelcast 3.x to the new Hazelcast Spring Session 4.x:

. Change the GroupId to `com.hazelcast` and artifactId to `hazelcast-spring-session`.
. Update your code and change the packages. All Hazelcast-specific classes were moved from `org.springframework.session.hazelcast` to `com.hazelcast.spring.session`.
. Remove this configuration for `PrincipalNameExtractor`:
+
[source,java]
----
AttributeConfig attributeConfig = new AttributeConfig()
.setName(Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
.setExtractorClassName(Hazelcast4PrincipalNameExtractor.class.getName());
config.getMapConfig(SESSIONS_MAP_NAME)
.addAttributeConfig(attributeConfig);
----
. Change serialization configuration. Replace:
+
[source,java]
----
SerializerConfig serializerConfig = new SerializerConfig();
serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class);
config.getSerializationConfig().addSerializerConfig(serializerConfig);
----
With a call:
+
[source,java]
----
config = HazelcastSessionConfiguration.applySerializationConfig(config);
----
. Index configuration for `PRINCIPAL_ATTRIBUTE_NAME` **if** no other IMap customization is wanted.

[[security-spring-configuration]]
== Configure Spring

Configure Spring to create a servlet filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session. Add the following Spring Configuration:

[source,java]
----
include::ROOT:example$/spring/HazelcastHttpSessionConfig.java[tags=config]
----
<1> The `@EnableHazelcastHttpSession` annotation creates a Spring bean named `springSessionRepositoryFilter` that implements `Filter`.
The filter replaces the `HttpSession` implementation to be backed by Spring Session.
In this instance, Spring Session is backed by Hazelcast.
<2> To serialize `BackingMapSession` objects efficiently, `HazelcastSessionCompactSerializer` and `AttributeValueCompactSerializer` needs to be registered. If this is not set, Hazelcast won't be able to deserialize session data.
<3> For best performance when using Hazelcast Spring Session with Spring Security, an index on `HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE` must be added. This step is optional in Hazelcast Spring Session 4.0, as `HazelcastIndexedSessionRepository` will try to add this index automatically. If you want to customize your `MapConfig`, you need to add this index manually as overriding `MapConfig` by `HazelcastIndexedSessionRepository` is not possible.
<4> Create a `HazelcastInstance` that connects Spring Session to Hazelcast.
By default, the application starts and connects to an embedded instance of Hazelcast.

== Customization options

To customize the SessionRepository your application will be using, declare a bean of type `SessionRepositoryCustomizer<HazelcastIndexedSessionRepository>`. For example:

[source,java]
----
@Bean
public SessionRepositoryCustomizer<HazelcastIndexedSessionRepository> customizeSessionRepo() {
return (sessionRepository) -> {
// here you can customize sessionRepository by calling the setter methods, for example:
sessionRepository.setFlushMode(FlushMode.IMMEDIATE);
sessionRepository.setSaveMode(SaveMode.ALWAYS);
sessionRepository.setDeployedOnAllMembers(false);
};
}
----

The following table describes some common customization options:

[cols="1m,1m,3",options="header"]
|===
| Method/property | Default value | Description

| flushMode
| FlushMode.ON_SAVE
| When changes in the session will be persisted. Possible options:

- `ON_SAVE` - `repository.save()` will persist the changes
- `IMMEDIATE` - changes will be persisted immediately, without the need to call `repository.save()`

| defaultMaxInactiveInterval
| 30 minutes
| Maximum inactive interval in between requests before the session will be invalidated. A negative time indicates that the session will never time out.

| sessionMapName
| `spring:session:sessions`
| Name of IMap used to store sessions.

| saveMode
| `SaveMode.ON_SET_ATTRIBUTE`
| When changes to session attributes will be saved. Possible values:

- `ON_SET_ATTRIBUTE` - save only changes made to the session. Best if you want to minimize risk of attribute override in highly concurrent environments.
- `ON_GET_ATTRIBUTE` - same as `ON_SET_ATTRIBUTE` plus save is performed also on read attributes.
- `ALWAYS` - always save all session attributes (even when not newly added or read).

| deployedOnAllMembers
| true
| Setting this property to `true` will perform session change processing server-side (on the Hazelcast members storing the data), which minimizes network traffic. However, it requires the Hazelcast Spring Session JAR to be deployed on all Hazelcast members in the cluster because `SessionUpdateEntryProcessor` will need to be deserialized on cluster members.

Setting the property to `false` will increase the network traffic and make session change processing slower (more steps involved: lock session key -> get fresh value -> process changes -> set new value -> unlock instead of just one step: send EntryProcessor to cluster), but will allow you to use Hazelcast Spring Session without the need of any server-side (member) changes.

| indexResolver
| `new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>())`
| IndexResolver to use when querying for `FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME`.

| sessionIdGenerator
| `UuidSessionIdGenerator.getInstance()`
| How session IDs for new sessions are generated.

| applicationEventPublisher
| no-op
| Allows setting a custom ApplicationEventPublisher that will be used to publish `AbstractSessionEvent`: `SessionCreatedEvent`,
`SessionExpiredEvent` and `SessionDeletedEvent`.

| disableSessionMapAutoConfiguration
| -
| If called, the autoconfiguration of IMap storing session data won't be performed and index on `HazelcastIndexedSessionRepository#PRINCIPAL_NAME_ATTRIBUTE` will not be created, potentially harming performance of the application. Can be used if you have pre-existing IMap configuration applied and do not want to convert it to `sessionMapConfigCustomizer`.

| sessionMapConfigCustomizer
| ConsumerEx.noop()
| Customizes session map configuration (MapConfig). If using client-server architecture, the customized `MapConfig` will not override pre-existing map configuration.

|===

== Supported topologies
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this in the right place? You'd presumably at least decide which topology to use before configuring anything. When would you do this config?


Hazelcast Spring Session supports two topologies.

=== Client-server

In this topology, the web application uses xref:clients:client-overview.adoc[Hazelcast clients] to connect to a Hazelcast cluster on a separate JVM. This may be a completely separate machine. xref:maintain-cluster:lite-members.adoc[Lite members] can optionally be used to on the application side.

image:HzSS-Client-Server.png[Hazelcast Spring Session with Client-Server topology]

To deploy in a client-server topology:

* Client side (Spring Boot application):
** Create a `@Bean` of type `ClientConfig` or `HazelcastInstance` that will return the result of `HazelcastClient.newHazelcastClient` method.

* Server side (Hazelcast member). This configuration is optional, but recommended.
** Deploy the Hazelcast Spring Session JAR onto the classpath with Spring context and Spring Security JARs.
** For best performance, add serializers to your Hazelcast configuration:
+
[tabs]
====
XML::
+
--
[source,xml]
----
<serialization>
<compact-serialization>
<serializers>
<serializer>com.hazelcast.spring.session.HazelcastSessionCompactSerializer</serializer>
<serializer>com.hazelcast.spring.session.AttributeValueCompactSerializer</serializer>
</serializers>
</compact-serialization>
</serialization>
----
--

YAML::
+
--
[source,yaml]
----
serialization:
compact-serialization:
serializers:
- serializer: com.hazelcast.spring.session.HazelcastSessionCompactSerializer
- serializer: com.hazelcast.spring.session.AttributeValueCompactSerializer
----
--

Java::
+
--
[source,java]
----
import com.hazelcast.spring.session.AttributeValueCompactSerializer;
import com.hazelcast.spring.session.HazelcastSessionCompactSerializer;
// ...
config.getSerializationConfig().getCompactSerializationConfig()
.addSerializer(new AttributeValueCompactSerializer())
.addSerializer(new HazelcastSessionCompactSerializer());
----

Alternatively, you can use the helper method:
[source,java]
----
import com.hazelcast.spring.session.HazelcastSessionConfiguration;
// ...
HazelcastSessionConfiguration.applySerializationConfig(config);
----
--
====

=== Embedded

In this topology, every web application instance has an embedded Hazelcast instance. Data is stored on the same JVMs as the application.

image:HzSS-Embedded.png[Hazelcast Spring Session with Embedded topology]

This topology is the easiest to start with as it does not require separate cluster JVMs, however it is less flexible because Hazelcast members scale with application instances.

To deploy in an embedded topology, you must configure serialization:
[tabs]
====
Java::
+
--
You can use the helper method (recommended):
[source,java]
----
import com.hazelcast.spring.session.HazelcastSessionConfiguration;
// ...
HazelcastSessionConfiguration.applySerializationConfig(config);
----
Alternatively, you can add specific serializers manually:
[source,java]
----
import com.hazelcast.spring.session.AttributeValueCompactSerializer;
import com.hazelcast.spring.session.HazelcastSessionCompactSerializer;
// ...
config.getSerializationConfig().getCompactSerializationConfig()
.addSerializer(new AttributeValueCompactSerializer())
.addSerializer(new HazelcastSessionCompactSerializer());
----
--

XML::
+
--
[source,xml]
----
<serialization>
<compact-serialization>
<serializers>
<serializer>com.hazelcast.spring.session.HazelcastSessionCompactSerializer</serializer>
<serializer>com.hazelcast.spring.session.AttributeValueCompactSerializer</serializer>
</serializers>
</compact-serialization>
</serialization>
----
--

YAML::
+
--
[source,yaml]
----
serialization:
compact-serialization:
serializers:
- serializer: com.hazelcast.spring.session.HazelcastSessionCompactSerializer
- serializer: com.hazelcast.spring.session.AttributeValueCompactSerializer
----
--

====

The `deployedOnAllMembers` option in `HazelcastIndexedSessionRepository` must be set to `true` (default), as in embedded mode every mode will always have Hazelcast Spring Session JAR on the classpath.
Loading
Loading