Spring Session Hazelcast
What You’ll Learn
In this tutorial, you will deploy two Spring Boot applications that use Hazelcast as a HTTP session store and hence share sessions among each other.
Spring Session manages users' session information and supports clustered sessions rather than an application container-specific solution only. That is, more than one application instance can use the same store for user sessions. You can find more details in the Spring Session Documentation.
Enable HazelcastHttpSession
Before using Hazelcast session repository, let’s add the required dependencies:
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>${hazelcast.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-hazelcast</artifactId>
</dependency>
Now configure the app to use Hazelcast as HTTP session store:
@Configuration
@EnableHazelcastHttpSession
class SessionConfiguration {
// ...
}
Alternatively, you may extend HazelcastHttpSessionConfiguration
to have more advanced configurations
rather than using annotation based configuration:
@Configuration
class SessionConfiguration extends HazelcastHttpSessionConfiguration {
// ...
}
Create a Hazelcast Instance Bean
After enabling Hazelcast HTTP Session, you need to provide a Hazelcast instance to be used by the session repository. This instance can be either a Hazelcast client or an embedded Hazelcast instance. You can see the details of client and embedded modes here on the documentation.
Notice some Hazelcast4
prefixes in the class names. If you use an older Hazelcast version (3.x), you
need to drop these "4"
s. For instance, use HazelcastIndexedSessionRepository
instead of
Hazelcast4IndexedSessionRepository
:
@Bean
@SpringSessionHazelcastInstance
public HazelcastInstance hazelcastInstance() {
Config config = new Config();
config.setClusterName("spring-session-cluster");
// Add this attribute to be able to query sessions by their PRINCIPAL_NAME_ATTRIBUTE's
AttributeConfig attributeConfig = new AttributeConfig()
.setName(Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
.setExtractorClassName(Hazelcast4PrincipalNameExtractor.class.getName());
// Configure the sessions map
config.getMapConfig(SESSIONS_MAP_NAME)
.addAttributeConfig(attributeConfig).addIndexConfig(
new IndexConfig(IndexType.HASH, Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE));
// Use custom serializer to de/serialize sessions faster. This is optional.
SerializerConfig serializerConfig = new SerializerConfig();
serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class);
config.getSerializationConfig().addSerializerConfig(serializerConfig);
return Hazelcast.newHazelcastInstance(config);
}
@Bean
@SpringSessionHazelcastInstance
public HazelcastInstance hazelcastInstance() {
ClientConfig clientConfig = new ClientConfig();
// Configure cluster member addresses to be connected.
clientConfig.getNetworkConfig().addAddress("127.0.0.1:5701");
// If spring-session packages do not present in Hazelcast member's classpath,
// these classes need to be deployed over the client. This is required since
// Hazelcast updates sessions via entry processors.
clientConfig.getUserCodeDeploymentConfig().setEnabled(true).addClass(Session.class)
.addClass(MapSession.class).addClass(Hazelcast4SessionUpdateEntryProcessor.class);
return HazelcastClient.newHazelcastClient(clientConfig);
}
You need to have a running Hazelcast cluster before starting a client. Also, to use all
session repository functionalities (e.g. findByPrincipalName), Hazelcast members in a cluster
must have the map configuration shown in the Embedded tab before they start. See the next
section for starting a Hazelcast cluster.
|
You need to enable user code deployment in member configurations as well: |
<user-code-deployment enabled="true">
<class-cache-mode>ETERNAL</class-cache-mode>
<provider-mode>LOCAL_AND_CACHED_CLASSES</provider-mode>
</user-code-deployment>
Start a Hazelcast Cluster
If you use Hazelcast client, you need to have a running cluster first. You can start Hazelcast members with one of the following methods:
You can easily create a Hazelcast cluster on Hazelcast Cloud with just a few clicks. See Getting Started documentation for details.
You can start members inside Docker containers. See the documentation for details.
$ docker run hazelcast/hazelcast:$HAZELCAST_VERSION
You can start members via Hazelcast CLI. See the documentation for the installation instructions and details.
$ hz start
You can start members via start
script in IMDG bundle.
$ sh bin/start.sh
You can find other ways of starting Hazelcast members and forming a cluster here. |
Using an Existing Instance
Let’s say you already have a Hazelcast instance created elsewhere in your application. You can configure Spring Session to use that instance instead of creating a new one:
@Bean
@SpringSessionHazelcastInstance
public HazelcastInstance hazelcastInstance() {
return Hazelcast.getHazelcastInstanceByName("existing-instance-name");
}
@Bean
@SpringSessionHazelcastInstance
public HazelcastInstance hazelcastInstance() {
return HazelcastClient.getHazelcastClientByName("existing-client-name");
}
Customize the Session Repository
Now that you have provided a Hazelcast instance, let’s configure the session repository further. Note that these configurations are optional and they will fall back to the defaults when not provided:
@Configuration
@EnableHazelcastHttpSession
class SessionConfiguration {
@Bean
public SessionRepositoryCustomizer<Hazelcast4IndexedSessionRepository> customize() {
return (sessionRepository) -> {
sessionRepository.setFlushMode(FlushMode.IMMEDIATE);
sessionRepository.setSaveMode(SaveMode.ALWAYS);
sessionRepository.setSessionMapName(SESSIONS_MAP_NAME);
sessionRepository.setDefaultMaxInactiveInterval(900);
};
}
// ...
}
@Configuration
class SessionConfiguration extends HazelcastHttpSessionConfiguration {
SessionConfiguration() {
setFlushMode(FlushMode.IMMEDIATE);
setSaveMode(SaveMode.ALWAYS);
setSessionMapName(SESSIONS_MAP_NAME);
setMaxInactiveIntervalInSeconds(900);
}
// ...
}
Interactions with User Sessions
Once you completed the configurations, you can reach to the session repository by autowiring it:
@Autowired
Hazelcast4IndexedSessionRepository sessionRepository;
Although you do not need to reach this repository explicitly to store or load sessions, some of the methods might be
needed such as findByIndexNameAndIndexValue
or findByPrincipalName
. Other than these, the following snippet will
load and store the sessions for you:
@GetMapping("/check")
public String check(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return "No session found.";
} else {
session.setAttribute("attr1", 1);
session.setMaxInactiveInterval(300);
return "Session found: " + session.getId();
}
}
And we are ready to test Hazelcast session store now!
Create the Endpoints
To test our session store behavior, create a few endpoints.
In SessionController.java
you can find 3 of them all of which are GET
mappings for simplicity:
-
/create
: If there is no session associated with the request, creates a new one with the principal attribute sent as the request parameter. -
/info
: Gives the session details if the request has a session. -
/list
: Lists all the sessions with the same principalName of the request’s session.
Start the Applications
Let’s now start two application instances to verify that both use the same session store:
SERVER_PORT=8080 mvn spring-boot:run
SERVER_PORT=8081 mvn spring-boot:run
Test the Applications
-
Create a new session on the port 8080:
-
See session info on the port 8081:
-
Now create another session with the same principal. But be aware that you need to use a different browser or use the private mode of your browser to use separate cookies:
-
Let’s list all the sessions with the principal
hazelcast
. You can imagine this scenario - for example, finding all the active sessions of a user: