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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 23:25:25