You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring非Boot应用中Hazelcast自定义序列化器未生效问题排查

问题:Hazelcast会话管理器自定义Portable序列化器未生效导致ClassNotFoundException

在非Spring Boot的Spring应用中使用Hazelcast会话管理器,采用客户端-服务器架构,因不想将自定义Jar包添加到Hazelcast服务器类路径,实现了自定义Portable序列化器,但运行后仍抛出如下序列化异常:

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)

提供的实现代码

1. Hazelcast会话配置类

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);
    }
}

2. CustomerDTO自定义序列化器

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;
    }
}

3. CustomerDTO类

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

使用版本:Hazelcast服务器5.3.2,管理中心5.3.1,Hazelcast客户端5.2.2


问题根源分析

  1. 序列化器注册方式错误:你实现的CustomerDTOPortableSerializer是PortableSerializer类型,但配置中却用addPortableFactory注册——这两者是完全不同的机制:PortableFactory用于创建实现了Portable接口的对象,而PortableSerializer是独立的序列化器,需要单独注册。
  2. DTO类不符合Portable规范:当前CustomerDTO仅实现了Serializable,未遵循Portable序列化的要求(若用PortableFactory模式,类必须实现Portable接口;若用PortableSerializer模式,需确保序列化器被正确绑定到目标类)。
  3. 版本不兼容:Hazelcast客户端5.2.2与服务器5.3.2存在版本差异,可能导致序列化协议不匹配,引发异常。

解决方案

方案一:正确注册PortableSerializer

  1. 修改客户端配置,直接添加CustomerDTOPortableSerializer,而非PortableFactory:
clientConfig.getSerializationConfig().addSerializer(new CustomerDTOPortableSerializer());
  1. 确保getTypeId()返回的ID全局唯一,且客户端与服务器的序列化配置中该ID对应的逻辑一致(因服务器无需加载自定义类,Portable序列化基于字段传输,无需服务器感知DTO类)。

方案二:改用Portable接口模式(推荐,更贴合Hazelcast规范)

  1. 修改CustomerDTO实现Portable接口:
public class CustomerDTO implements Portable {
    private Long id;
    private boolean authenticated;
    private int numberOfOrders;
    private List<Long> ownedProductIds;

    @Override
    public int getFactoryId() {
        return PortableCustomerDTOFactory.FACTORY_ID; // 例如设为1001
    }

    @Override
    public int getClassId() {
        return 1; // 该工厂下唯一的类ID
    }

    @Override
    public void writePortable(PortableWriter writer) throws IOException {
        writer.writeLong("id", id);
        writer.writeBoolean("authenticated", authenticated);
        writer.writeInt("numberOfOrders", numberOfOrders);
        writer.writeLongArray("ownedProductIds", ownedProductIds.toArray(new Long[0]));
    }

    @Override
    public void readPortable(PortableReader reader) throws IOException {
        id = reader.readLong("id");
        authenticated = reader.readBoolean("authenticated");
        numberOfOrders = reader.readInt("numberOfOrders");
        ownedProductIds = Arrays.asList(reader.readLongArray("ownedProductIds"));
    }

    // getter和setter方法
}
  1. 实现对应的PortableFactory:
public class PortableCustomerDTOFactory implements PortableFactory {
    public static final int FACTORY_ID = 1001;

    @Override
    public Portable create(int classId) {
        if (classId == 1) {
            return new CustomerDTO();
        }
        return null;
    }
}
  1. 在客户端配置中正确注册该Factory:
clientConfig.getSerializationConfig().addPortableFactory(PortableCustomerDTOFactory.FACTORY_ID, new PortableCustomerDTOFactory());

额外注意事项

  • 统一Hazelcast客户端与服务器版本,建议同步升级到5.3.2或降级到5.2.2,避免版本差异导致的序列化协议冲突。
  • 确认Spring Session版本与Hazelcast版本兼容,例如Spring Session for Hazelcast需匹配对应Hazelcast版本。

内容的提问来源于stack exchange,提问作者lawd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 09:27:58