在Java中使用Protobuf实现自定义Java类型的序列化与反序列化
Protocol Buffer自定义类型序列化/反序列化方案(JTS Geometry场景)
注:你的Proto定义中end_locaion字段号与start_locaion重复(都是3),会导致编译错误,建议修改为4:
syntax = "proto3"; import "google/protobuf/timestamp.proto"; message UberDrive { string user_id = 1; google.protobuf.Timestamp transaction_timestamp = 2; string start_locaion = 3; //WKT data string end_locaion = 4; //WKT data }
针对你希望在POJO中直接使用org.locationtech.jts.geom.Geometry类型、自动处理WKT与Geometry转换的需求,以下是几种可行方案:
方案1:MapStruct自动映射(简单易落地)
利用MapStruct的自动代码生成能力,定义映射规则后自动完成Proto消息与业务POJO的转换,无需手动编写重复的序列化/反序列化逻辑。
步骤:
- 引入MapStruct依赖(以Maven为例)
<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.5.5.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.5.5.Final</version> <scope>provided</scope> </dependency>
- 定义业务POJO(直接使用Geometry类型)
import org.locationtech.jts.geom.Geometry; import java.time.Instant; public class UberDrivePOJO { private String userId; private Instant transactionTimestamp; private Geometry startLocation; private Geometry endLocation; // 生成getter/setter }
- 定义MapStruct映射接口,嵌入WKT与Geometry的转换逻辑
import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.io.WKTReader; import org.locationtech.jts.io.WKTWriter; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Named; import org.mapstruct.factory.Mappers; @Mapper public interface UberDriveMapper { UberDriveMapper INSTANCE = Mappers.getMapper(UberDriveMapper.class); // Proto消息转业务POJO:WKT字符串转Geometry @Mapping(source = "startLocaion", target = "startLocation", qualifiedByName = "wktToGeometry") @Mapping(source = "endLocaion", target = "endLocation", qualifiedByName = "wktToGeometry") UberDrivePOJO protoToPojo(UberDrive proto); // 业务POJO转Proto消息:Geometry转WKT字符串 @Mapping(source = "startLocation", target = "startLocaion", qualifiedByName = "geometryToWkt") @Mapping(source = "endLocation", target = "endLocaion", qualifiedByName = "geometryToWkt") UberDrive pojoToProto(UberDrivePOJO pojo); // 自定义WKT转Geometry方法 @Named("wktToGeometry") default Geometry wktToGeometry(String wkt) { if (wkt == null || wkt.isBlank()) { return null; } try { return new WKTReader().read(wkt); } catch (Exception e) { throw new IllegalArgumentException("无效的WKT格式: " + wkt, e); } } // 自定义Geometry转WKT方法 @Named("geometryToWkt") default String geometryToWkt(Geometry geometry) { if (geometry == null) { return null; } return new WKTWriter().write(geometry); } }
- 实际使用
// Proto转POJO UberDrive protoMsg = UberDrive.newBuilder().setStartLocaion("POINT(116.4038739 39.914885)").build(); UberDrivePOJO pojo = UberDriveMapper.INSTANCE.protoToPojo(protoMsg); // POJO转Proto UberDrivePOJO pojo = new UberDrivePOJO(); pojo.setStartLocation(new WKTReader().read("POINT(116.4038739 39.914885)")); UberDrive protoMsg = UberDriveMapper.INSTANCE.pojoToProto(pojo);
方案2:自定义Protobuf代码生成插件(深度定制)
如果希望直接让Protobuf生成的Java类就包含Geometry字段(而非string),可以编写protoc的Java代码生成插件,识别带有//WKT data注释的string字段,生成时自动替换为Geometry类型,并内置WKT序列化/反序列化逻辑。
核心思路:
- 继承
com.google.protobuf.compiler.PluginProtos.CodeGenerator接口,实现自定义代码生成逻辑 - 解析Proto文件的AST,筛选出标记为WKT的string字段
- 生成对应的Geometry字段,并添加从WKT字符串转换的代码(调用
WKTReader/WKTWriter) - 编译插件后,配置protoc使用该插件生成定制化的Java类
此方案需要熟悉Protobuf代码生成API,实现成本较高,但无需额外的映射层,直接使用生成的类即可。
方案3:Jackson/Gson TypeAdapter(适用于JSON序列化场景)
如果项目中使用JSON作为中间格式,可以自定义TypeAdapter,让序列化框架自动处理Geometry与WKT的转换。
以Gson为例:
import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.io.WKTReader; import org.locationtech.jts.io.WKTWriter; import java.io.IOException; public class GeometryTypeAdapter extends TypeAdapter<Geometry> { private final WKTReader wktReader = new WKTReader(); private final WKTWriter wktWriter = new WKTWriter(); @Override public void write(JsonWriter out, Geometry value) throws IOException { if (value == null) { out.nullValue(); return; } out.value(wktWriter.write(value)); } @Override public Geometry read(JsonReader in) throws IOException { String wkt = in.nextString(); if (wkt == null || wkt.isBlank()) { return null; } try { return wktReader.read(wkt); } catch (Exception e) { throw new IOException("解析WKT失败: " + wkt, e); } } }
注册后即可直接序列化/反序列化带有Geometry字段的POJO:
import com.google.gson.Gson; import com.google.gson.GsonBuilder; Gson gson = new GsonBuilder() .registerTypeAdapter(Geometry.class, new GeometryTypeAdapter()) .create(); // POJO转JSON String json = gson.toJson(pojo); // JSON转POJO UberDrivePOJO pojo = gson.fromJson(json, UberDrivePOJO.class);
内容的提问来源于stack exchange,提问作者Elad Barkay
相关产品推荐
相关产品推荐

