Java11+Protobuf3实现JSON与Protobuf双向转换及请求构建
实现Jersey JSON代理端点(Protobuf双向转换)
基于Java 11和Protocol Buffers 3,你可以借助Protobuf官方的JSON转换工具,结合Jersey的自定义消息体提供者来实现需求,以下是可靠的落地方案及示例:
1. 依赖配置
首先在Maven项目中引入核心依赖,包含Jersey框架、Protobuf核心库及官方JSON转换工具:
<dependencies> <!-- Jersey核心依赖 --> <dependency> <groupId>org.glassfish.jersey.core</groupId> <artifactId>jersey-server</artifactId> <version>2.41</version> </dependency> <dependency> <groupId>org.glassfish.jersey.core</groupId> <artifactId>jersey-client</artifactId> <version>2.41</version> </dependency> <!-- Protobuf核心及JSON转换工具 --> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>3.25.3</version> </dependency> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java-util</artifactId> <version>3.25.3</version> </dependency> </dependencies>
2. 自定义Protobuf-JSON消息体提供者
Jersey默认不支持Protobuf与JSON的双向转换,需自定义MessageBodyReader和MessageBodyWriter,利用Protobuf官方的JsonFormat工具完成转换:
import com.google.protobuf.GeneratedMessageV3; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.util.JsonFormat; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.Produces; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.ext.MessageBodyReader; import jakarta.ws.rs.ext.MessageBodyWriter; import jakarta.ws.rs.ext.Provider; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Type; @Provider @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public class ProtobufJsonProvider implements MessageBodyReader<GeneratedMessageV3>, MessageBodyWriter<GeneratedMessageV3> { private final JsonFormat.Parser jsonParser = JsonFormat.parser().ignoringUnknownFields(); private final JsonFormat.Printer jsonPrinter = JsonFormat.printer().includingDefaultValueFields(); @Override public boolean isReadable(Class<?> type, Type genericType, jakarta.ws.rs.core.Annotation[] annotations, MediaType mediaType) { return GeneratedMessageV3.class.isAssignableFrom(type); } @Override public GeneratedMessageV3 readFrom(Class<GeneratedMessageV3> type, Type genericType, jakarta.ws.rs.core.Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException { try { GeneratedMessageV3.Builder builder = (GeneratedMessageV3.Builder) type.getMethod("newBuilder").invoke(null); jsonParser.merge(new String(entityStream.readAllBytes()), builder); return (GeneratedMessageV3) builder.build(); } catch (Exception e) { throw new WebApplicationException("JSON转Protobuf失败", e); } } @Override public boolean isWriteable(Class<?> type, Type genericType, jakarta.ws.rs.core.Annotation[] annotations, MediaType mediaType) { return GeneratedMessageV3.class.isAssignableFrom(type); } @Override public void writeTo(GeneratedMessageV3 proto, Class<?> type, Type genericType, jakarta.ws.rs.core.Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException { try { String json = jsonPrinter.print(proto); entityStream.write(json.getBytes()); } catch (InvalidProtocolBufferException e) { throw new WebApplicationException("Protobuf转JSON失败", e); } } @Override public long getSize(GeneratedMessageV3 proto, Class<?> type, Type genericType, jakarta.ws.rs.core.Annotation[] annotations, MediaType mediaType) { return -1; // 由Jersey自动处理内容长度 } }
3. 注册提供者到Jersey
通过ResourceConfig注册自定义提供者及代理资源类:
import org.glassfish.jersey.server.ResourceConfig; public class JerseyConfig extends ResourceConfig { public JerseyConfig() { // 注册Protobuf-JSON转换提供者 register(ProtobufJsonProvider.class); // 注册代理资源 register(PersonProxyResource.class); register(SubscriptionProxyResource.class); } }
4. 实现代理端点
编写代理资源类,调用原Protobuf API并完成格式转换:
Person代理端点
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.util.JsonFormat; import jakarta.ws.rs.*; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; @Path("/proxy") public class PersonProxyResource { private final Client client = ClientBuilder.newClient(); private static final String BASE_PROTOBUF_API = "http://localhost:8080/"; @GET @Path("/getPerson") @Produces(MediaType.APPLICATION_JSON) public String getPerson() { // 调用原Protobuf API Response response = client.target(BASE_PROTOBUF_API + "getPerson") .request(MediaType.APPLICATION_OCTET_STREAM) .get(); PersonProto person = response.readEntity(PersonProto.class); // 转换为JSON返回 try { return JsonFormat.printer().includingDefaultValueFields().print(person); } catch (InvalidProtocolBufferException e) { throw new WebApplicationException("Protobuf转JSON失败", e); } } @POST @Path("/person") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public String createPerson(String jsonRequest) { // JSON转Protobuf PersonProto.Builder builder = PersonProto.newBuilder(); try { JsonFormat.parser().ignoringUnknownFields().merge(jsonRequest, builder); PersonProto person = builder.build(); // 调用原Protobuf API创建数据 Response response = client.target(BASE_PROTOBUF_API + "person") .request(MediaType.APPLICATION_OCTET_STREAM) .post(Entity.entity(person, MediaType.APPLICATION_OCTET_STREAM)); PersonProto createdPerson = response.readEntity(PersonProto.class); // 转换为JSON返回 return JsonFormat.printer().includingDefaultValueFields().print(createdPerson); } catch (InvalidProtocolBufferException e) { throw new WebApplicationException("JSON-Protobuf转换失败", e); } } }
Subscription代理端点
注意:Protobuf不支持直接返回List类型,原API需用包装消息(如SubscriptionListProto)承载列表数据
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.util.JsonFormat; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; @Path("/proxy") public class SubscriptionProxyResource { private final Client client = ClientBuilder.newClient(); private static final String BASE_PROTOBUF_API = "http://localhost:8080/"; @GET @Path("/getSubscriptions") @Produces(MediaType.APPLICATION_JSON) public String getSubscriptions(@QueryParam("personId") String personId) { // 调用原Protobuf API Response response = client.target(BASE_PROTOBUF_API + "getSubscriptions") .queryParam("personId", personId) .request(MediaType.APPLICATION_OCTET_STREAM) .get(); SubscriptionListProto subscriptionList = response.readEntity(SubscriptionListProto.class); // 转换为JSON返回 try { return JsonFormat.printer().includingDefaultValueFields().print(subscriptionList); } catch (InvalidProtocolBufferException e) { throw new WebApplicationException("Protobuf列表转JSON失败", e); } } }
对应的Protobuf包装消息定义示例:
message SubscriptionListProto { repeated SubscriptionProto subscriptions = 1; }
关键说明
JsonFormat配置:ignoringUnknownFields()可兼容客户端JSON中的多余字段;includingDefaultValueFields()会输出字段默认值(如int的0、string的空串),无需可移除。- 原API调用:因原接口返回Protobuf二进制数据,请求/响应的MediaType需指定为
APPLICATION_OCTET_STREAM。 - 异常处理:将转换过程中的异常包装为
WebApplicationException,统一返回给客户端。
内容的提问来源于stack exchange,提问作者Rustem K
相关产品推荐
相关产品推荐

