Search code examples
springhazelcast

Using custom Serializer in Hazelcast Spring application


I am currently using Hazelcast based session manager in my spring(not spring boot) application. I currently hit this problem:

com.hazelcast.nio.serialization.HazelcastSerializationException: java.lang.ClassNotFoundException: com.thg.role.dto.CustomerDTO 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 com.hazelcast.internal.serialization.impl.StreamSerializerAdapter.read(StreamSerializerAdapter.java:44)

I do not want to add a custom jar to the Hazelcast server classpath(My set up is a server-client one). Instead I need a way that Hazelcast can still serialize and deserialize the object. After reading up on Hazelcast documentation, I realized I had to implement a custom Portable serializer. Below is my implementation:

import static org.springframework.session.hazelcast.HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME;
import static org.springframework.session.hazelcast.HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE;


@Configuration
@EnableHazelcastHttpSession
public class HazelCastSessionConfig {
    private static final Logger LOGGER = LoggerFactory.getLogger(HazelCastSessionConfig.class);
    @Bean
    public SessionRepositoryCustomizer<Hazelcast4IndexedSessionRepository> sessionRepositoryCustomizer() {
        return sessionRepository -> {
            sessionRepository.setFlushMode(FlushMode.IMMEDIATE);
            sessionRepository.setSaveMode(SaveMode.ON_SET_ATTRIBUTE);
            sessionRepository.setSessionMapName(DEFAULT_SESSION_MAP_NAME);
            sessionRepository.setDefaultMaxInactiveInterval(1200);
        };
    }

    @Bean
    @SpringSessionHazelcastInstance
    public HazelcastInstance hazelcastInstance() {

        ClientConfig clientConfig = new ClientConfig();
        clientConfig.setClusterName("dev");
        clientConfig.setProperty("hazelcast.session.replication.enabled", "true");
        clientConfig.setProperty("hazelcast.logging.type", "slf4j");

        clientConfig.getNetworkConfig().addAddress("12.30.140.28");
        clientConfig.getSerializationConfig().addPortableFactory(PortableAddressDTOFactory.FACTORY_ID, new PortableAddressDTOFactory());
        clientConfig.getSerializationConfig().addPortableFactory(PortableCustomerDTOFactory.FACTORY_ID, new PortableCustomerDTOFactory());

        var hazelcastClient = HazelcastClient.newHazelcastClient(clientConfig);
        configureSessionMap(hazelcastClient);
        return hazelcastClient;
    }

    private void configureSessionMap(HazelcastInstance hazelcastClient) {
        var mapCfg = new MapConfig(DEFAULT_SESSION_MAP_NAME);

        var attributeConfig = new AttributeConfig()
            .setName(PRINCIPAL_NAME_ATTRIBUTE)
            .setExtractorClassName(Hazelcast4PrincipalNameExtractor.class.getName());

        var indexConfig = new IndexConfig(IndexType.HASH, PRINCIPAL_NAME_ATTRIBUTE);

        mapCfg.addAttributeConfig(attributeConfig).addIndexConfig(indexConfig);

        hazelcastClient.getConfig().addMapConfig(mapCfg);
    }
}


public class CustomerDTOPortableSerializer implements PortableSerializer<CustomerDTO> {

    @Override
    public int getTypeId() {
        // A unique ID for this Portable serializer
        return 1;
    }

    @Override
    public void write(PortableWriter writer, CustomerDTO customerDTO) throws IOException {
        // Write the fields of the CustomerDTO object to the PortableWriter
        writer.writeLong("id", customerDTO.getId());
        writer.writeBoolean("authenticated", customerDTO.isAuthenticated());
        writer.writeInt("numberOfOrders", customerDTO.getNumberOfOrders());
        writer.writeLongArray("ownedProductIds", customerDTO.getOwnedProductIds().toArray(new Long[0]));
    }

    @Override
    public CustomerDTO read(PortableReader reader) throws IOException {
        // Read the fields from the PortableReader and create a new //CustomerDTO object
        CustomerDTO customerDTO = new CustomerDTO();
        customerDTO.setId(reader.readLong("id"));
        customerDTO.setAuthenticated(reader.readBoolean("authenticated"));
        customerDTO.setNumberOfOrders(reader.readInt("numberOfOrders"));
        customerDTO.setOwnedProductIds(Arrays.asList(reader.readLongArray("ownedProductIds")));
        return customerDTO;
    }
}

CustomerDTO implements Serializable {
//fields, getters and setters..
}

However, when I ran the application, it is still throwing the same error. It looks like my custom Portable class was not registered. Anyone willing to point me to what the problem might be? Hazelcast version: 5.3.2 Management centre version: 5.3.1 Hazelcast client version: 5.2.2

I have tried implementing my own custom Portable serializers but not do not think they are being picked up by the Hazelcast server as the error persists.


Solution

  • That is probably happening because of the misuse of Portable. I see that your class does not implement the Portable interface. It should implement it, not the Serializable.

    public class CustomerDTO implements Portable {
    
    }
    

    See this full example in the reference manual to see how the class and its factory should look like:

    https://docs.hazelcast.com/hazelcast/latest/serialization/implementing-portable-serialization#portable-serialization-example-code

    If you don't want your class to implement a Hazelcast-specific interface, then check out the Compact serialization: