Spring Boot Native Image无法序列化Protobuf对象求助
我有如下方法:
@Cacheable(value = USER_IDENTITIES_PROTO_CACHE) public UserIdentitiesResponseOuterClass.UserIdentitiesResponse findUserIdentitiesProto(UUID userId) {
其中UserIdentitiesResponseOuterClass.UserIdentitiesResponse是Protobuf生成的类。JVM模式下运行一切正常,但在Native Image环境中抛出异常:
"Failure putting into cache: user_identities_proto. Cannot serialize"
我尝试添加了以下Runtime Hints代码,但问题并未解决:
hints.serialization() .registerType(TypeReference.of(UserIdentitiesResponseOuterClass.class)) .registerType(UserIdentitiesResponseOuterClass.UserIdentity.class) .registerType(UserIdentitiesResponseOuterClass.UserIdentitiesResponse.class);
我使用Redis作为缓存,序列化方式为JdkSerializationRedisSerializer。
Proto文件内容如下:
syntax = "proto3"; package identity; message UserIdentity { string provider = 1; string sub = 2; } message UserIdentitiesResponse { UserIdentity primary = 1; repeated UserIdentity identities = 2; }
JVM模式下Protobuf对象的序列化/反序列化都正常,但Native Image模式下就出现上述序列化失败的问题。我认为应该补充Runtime Hints,但已经尝试了上面的代码,不清楚还缺什么,求解决办法?
1. 补全Protobuf类的序列化相关Runtime Hints
JDK序列化依赖类的无参构造器、writeObject/readObject等序列化方法,但Native Image默认不会自动保留这些内容,哪怕Protobuf生成的类自带这些逻辑。你需要额外注册以下内容:
- 每个Protobuf生成类的无参构造器
- 序列化过程中可能用到的Builder内部类
- repeated字段对应的数组类型(比如
UserIdentity[])
修改Runtime Hints代码如下:
hints.serialization() // 注册主响应类 .registerType(UserIdentitiesResponseOuterClass.UserIdentitiesResponse.class) // 注册嵌套的UserIdentity类 .registerType(UserIdentitiesResponseOuterClass.UserIdentity.class) // 注册repeated字段对应的数组类型 .registerType(UserIdentitiesResponseOuterClass.UserIdentity[].class) // 注册Builder类,JDK序列化过程中可能会用到 .registerType(UserIdentitiesResponseOuterClass.UserIdentitiesResponse.Builder.class) .registerType(UserIdentitiesResponseOuterClass.UserIdentity.Builder.class); // 单独注册无参构造器,确保Native Image能调用到 hints.reflection() .registerConstructor(UserIdentitiesResponseOuterClass.UserIdentitiesResponse.class.getDeclaredConstructor(), ExecutableMode.INVOKE) .registerConstructor(UserIdentitiesResponseOuterClass.UserIdentity.class.getDeclaredConstructor(), ExecutableMode.INVOKE);
2. 换用Protobuf专用序列化器(更推荐)
JdkSerializationRedisSerializer本身效率低,对Native Image的兼容性也差,不如直接换用Protobuf专用的序列化方式。比如基于Protobuf原生API自定义序列化器:
示例:自定义Protobuf Redis序列化器
@Bean public RedisSerializer<Object> protobufRedisSerializer() { return new RedisSerializer<>() { @Override public byte[] serialize(Object o) throws SerializationException { if (o instanceof Message) { return ((Message) o).toByteArray(); } throw new SerializationException("只支持Protobuf Message类型的对象"); } @Override public Object deserialize(byte[] bytes) throws SerializationException { return UserIdentitiesResponseOuterClass.UserIdentitiesResponse.parseFrom(bytes); } }; }
然后给RedisTemplate配置这个序列化器:
@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory, RedisSerializer<Object> protobufRedisSerializer) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(protobufRedisSerializer); template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(protobufRedisSerializer); return template; }
3. 手动添加反射配置文件
如果坚持使用JDK序列化,可以在项目的src/main/resources/META-INF/native-image目录下添加reflect-config.json文件,手动指定要保留的Protobuf类的反射信息:
[ { "name": "identity.UserIdentitiesResponseOuterClass$UserIdentitiesResponse", "allDeclaredConstructors": true, "allDeclaredMethods": true, "allDeclaredFields": true }, { "name": "identity.UserIdentitiesResponseOuterClass$UserIdentity", "allDeclaredConstructors": true, "allDeclaredMethods": true, "allDeclaredFields": true }, { "name": "identity.UserIdentitiesResponseOuterClass$UserIdentity[]" } ]
内容的提问来源于stack exchange,提问作者Денис Анд

