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
问题根源分析
- 序列化器注册方式错误:你实现的
CustomerDTOPortableSerializer是PortableSerializer类型,但配置中却用addPortableFactory注册——这两者是完全不同的机制:PortableFactory用于创建实现了Portable接口的对象,而PortableSerializer是独立的序列化器,需要单独注册。 - DTO类不符合Portable规范:当前
CustomerDTO仅实现了Serializable,未遵循Portable序列化的要求(若用PortableFactory模式,类必须实现Portable接口;若用PortableSerializer模式,需确保序列化器被正确绑定到目标类)。 - 版本不兼容:Hazelcast客户端5.2.2与服务器5.3.2存在版本差异,可能导致序列化协议不匹配,引发异常。
解决方案
方案一:正确注册PortableSerializer
- 修改客户端配置,直接添加
CustomerDTOPortableSerializer,而非PortableFactory:
clientConfig.getSerializationConfig().addSerializer(new CustomerDTOPortableSerializer());
- 确保
getTypeId()返回的ID全局唯一,且客户端与服务器的序列化配置中该ID对应的逻辑一致(因服务器无需加载自定义类,Portable序列化基于字段传输,无需服务器感知DTO类)。
方案二:改用Portable接口模式(推荐,更贴合Hazelcast规范)
- 修改
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方法 }
- 实现对应的
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; } }
- 在客户端配置中正确注册该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
相关产品推荐
相关产品推荐

