G4iner
G4iner

Reputation: 31

Problem with HttpSession serialization (ClassNotFoundException) after migration to Spring Session 3 and Hazelcast 5

I am trying to migrate from Spring Boot 2.7 application with Hazelcast 4.2 to Spring Boot 3.2 and Hazelcast 5.3, which also includes JCache Hazelcast provider.

In previous version I was using WebFilter from Hazelcast (https://github.com/hazelcast/hazelcast-wm/blob/master/src/main/java/com/hazelcast/web/WebFilter.java) registered with FilterRegistrationBean, but it is not prepared for Spring Boot 3 bacause of the lack of jakarta imports.

Because of that I wanted to use Spring Session Hazelcast 3 configuration, but after making request to application I am getting this stacktrace:

com.hazelcast.nio.serialization.HazelcastSerializationException: java.lang.ClassNotFoundException: org.springframework.session.hazelcast.SessionUpdateEntryProcessor
    at com.hazelcast.internal.serialization.impl.defaultserializers.JavaDefaultSerializers$JavaSerializer.read(JavaDefaultSerializers.java:96)
    at com.hazelcast.internal.serialization.impl.defaultserializers.JavaDefaultSerializers$JavaSerializer.read(JavaDefaultSerializers.java:85)
    at (...)
Caused by: java.lang.ClassNotFoundException: org.springframework.session.hazelcast.SessionUpdateEntryProcessor
    at jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
(...)

So then I added configuration for the hazelcast server and client to use "User Code Deployment" as stated in the documentation (along with Spring one): https://docs.hazelcast.com/hazelcast/5.3/clusters/deploying-code-on-member but then I get error on missing class with @SessionScope - do I have to add jar of classes?

com.hazelcast.core.HazelcastException: java.lang.NoClassDefFoundError: com/g4iner/ExampleCache$Companion
    at com.hazelcast.internal.util.ExceptionUtil.lambda$static$0(ExceptionUtil.java:48)

I get this error after following this documentantion, but I'm not sure if I adjusted this in a right way: https://docs.spring.io/spring-session/reference/3.2/http-session.html#httpsession-hazelcast

There is also some information on Hazelcast but it lacks changes for new versions: https://docs.hazelcast.com/tutorials/spring-session-hazelcast

My configuration and code (using Spring Boot 3.2.6, Hazelcast 5.3.7 client, hazelcast 5.3.7 server running in docker-compose):

docker-compose.yml

version: "3"
services:
  hazelcast:
    image: hazelcast/hazelcast:5.3.7-slim-jdk17
    environment:
      - HZ_USERCODEDEPLOYMENT_ENABLED=true
    ports:
      - "5701:5701"

pom.xml

org.springframework.boot:spring-boot-starter-web-3.2.6
org.springframework.boot:spring-boot-starter-cache-3.2.6
org.springframework.session:spring-session-core-3.2.3
org.springframework.session:spring-session-hazelcast-3.2.3
javax.cache:cache-api-1.1.1
com.hazelcast:hazelcast-spring-5.3.7
com.hazelcast:hazelcast-5.3.7

application.yml

spring:
  cache:
    type: jcache
    jcache:
      provider: com.hazelcast.client.cache.HazelcastClientCachingProvider
  hazelcast:
    config: classpath:hazelcast-client.xml

hazelcast-client.xml

<hazelcast-client xmlns="http://www.hazelcast.com/schema/client-config"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xsi:schemaLocation="http://www.hazelcast.com/schema/client-config
                  http://www.hazelcast.com/schema/client-config/hazelcast-client-config-5.3.xsd">

    <cluster-name>dev</cluster-name>

    <instance-name>hazelcast-client</instance-name>

    <user-code-deployment enabled="true">
        <classNames>
           <className>org.springframework.session.hazelcast.SessionUpdateEntryProcessor</className>
            <className>org.springframework.session.Session</className>
            <className>org.springframework.session.MapSession</className>
        </classNames>
    </user-code-deployment>

    <network>
        <cluster-members>
            <address>localhost:5701</address>
        </cluster-members>
    </network>

</hazelcast-client>

HazelcastSessionConfiguration.kt

@EnableHazelcastHttpSession
@Configuration
open class HazelcastSessionConfiguration {

@EnableHazelcastHttpSession
@Configuration
open class HazelcastSessionConfiguration {

    @Bean
    @SpringSessionHazelcastInstance
    open fun hazelcastInstance(): HazelcastInstance {
        val clientConfig = ClientConfig()
        clientConfig.networkConfig.addAddress("localhost:5701")
        clientConfig.userCodeDeploymentConfig.setEnabled(true).addClass(Session::class.java)
            .addClass(MapSession::class.java).addClass(SessionUpdateEntryProcessor::class.java)
        return HazelcastClient.newHazelcastClient(clientConfig)
    }


// example config from Spring docs for Server - what and how do I need for docker instance?
    @Bean
    open fun hazelcastConfig(): Config {
        val config = Config()
        val attributeConfig = AttributeConfig()
            .setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
            .setExtractorClassName(PrincipalNameExtractor::class.java.name)
        config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME)
            .addAttributeConfig(attributeConfig)
            .addIndexConfig(
                IndexConfig(IndexType.HASH, HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
            )
        config.setClusterName("dev")
        config.networkConfig.setPublicAddress("localhost:5701")
        val serializerConfig = SerializerConfig()
        serializerConfig.setImplementation(HazelcastSessionSerializer()).setTypeClass(MapSession::class.java)
        config.serializationConfig.addSerializerConfig(serializerConfig)
        return config
    }

}

I tried different configurations,with and without "hazelcastConfig" bean, changing hazelcast to 5.4 and 4.2, or creating "hazelcastInstance" but nothing changes.

What am I missing here, what is the proper way of configuring client and server? Any help will be appreciated

Upvotes: 0

Views: 238

Answers (1)

Or&#231;un &#199;olak
Or&#231;un &#199;olak

Reputation: 702

If you are using Hazelcast as a client, then the "user code deployment" feature needs to be enabled. Because spring session is using Hazelcast's entry processor to update the session data.

On the cluster side you need to change the docker-compose.yaml file something like this. Here I am using hazelcast:5.4.0 image because Spring 3 requires Java 17. As far as I know Hazelcast 5.3.x uses Java 8 and it does not allow a class compiled with a newer Java version to be deployed.

version: "3"
services:
  hazelcast:
    image: hazelcast/hazelcast:5.4.0-slim-jdk21
    ports:
      - "5701:5701"
    environment:
      - HZ_USERCODEDEPLOYMENT_ENABLED=true

On the client side you need to deploy some spring classes to cluster, because Hazelcast cluster does not have them. Something like this.

import com.hazelcast.client.HazelcastClient;
import com.hazelcast.client.config.ClientConfig;
import com.hazelcast.core.HazelcastInstance;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.session.SessionIdGenerator;
import org.springframework.session.hazelcast.SessionUpdateEntryProcessor;


@Configuration
public class HazelcastHttpSessionConfig {

    @Bean
    public HazelcastInstance hazelcastInstance() {
        ClientConfig clientConfig = new ClientConfig();
        clientConfig.getNetworkConfig()
                .addAddress("0.0.0.0:5701"); // connect to local server

        clientConfig.getUserCodeDeploymentConfig()
                .setEnabled(true)
                .addClass(Session.class)
                .addClass(MapSession.class)
                .addClass(SessionUpdateEntryProcessor.class)
                .addClass(SessionIdGenerator.class)
        ;
        return HazelcastClient.newHazelcastClient(clientConfig);
    }
}

application.properties is like this

spring.cache.type=jcache
spring.cache.jcache.provider: com.hazelcast.client.cache.HazelcastClientCachingProvider

That's all. You don't need to do anything else.

Note : This answer only works if you are using spring-session only. If you are using spring-session + spring-security you also need to deploy spring-security related classes to Hazelcast cluster

Upvotes: 1

Related Questions