一步搞定Proto生成的Bean和JavaBean的序列化

1,197 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情

引言

公司业务里涉及到 Proto 生成的类很多,原因是我们需要和其他部门进行 Grpc 通信,用的也是 proto + grpc 这一套,相信知道的同学都会被 proto 生产的类和 pojo 类转换问题而困扰,首先为了解耦都会用 dto 去转一层,我们当时用的 BeanUtils 进行对象直接的拷贝赋值,但是它有个弊端就是无法深拷贝,而且数组的拷贝也有些不雅,于是我调研了下Google 的Gson,想着 Gson 和 Proto 是一家的应该有相关联的操作吧,然后不出我所料

依赖

首先你要导入依赖

       <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.8</version>
        </dependency>
 <!-- Protobuf与Json的相互转化 -->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java-util</artifactId>
            <version>3.17.2</version>
        </dependency>

工具类如下,粘贴即用

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.google.protobuf.LazyStringArrayList;
import com.google.protobuf.LazyStringList;
import com.google.protobuf.Message;
import com.google.protobuf.MessageOrBuilder;
import com.google.protobuf.util.JsonFormat;
import lombok.SneakyThrows;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * @desc: JavaBean 和 ProtoBean 相互转换工具类
 * 适用场景
 * 1、JavaBean 转 ProtoBean 、 ProtoBean 转 JavaBean  、JavaBean 装 JavaBean JavaBeanList 转 JavaBeanList
 * <p>
 * 2、JavaBeanList 转 ProtoBeanList  、ProtoBeanList 转 JavaBeanList
 * <p>
 * 3、JavaBean 转 ProtoBuilder 、 JavaBeanList 转 ProtoBuilderList
 * <p>
 * 4、ProtoBean 转 ProtoBean 、ProtoBeanList 转 ProtoBeanList
 * <p>
 * 5、Json 转 JavaBean 、JavaBean 转 Json  、Json 转 JavaBeanList
 */
public class ProtoJsonUtils {
    private static Gson gson = getGson();

    /**
     * protoBean -> string
     *
     * @param sourceMessage protoBean
     * @return string
     */
    public static String protoToJson(MessageOrBuilder sourceMessage) {
        try {
            return JsonFormat.printer().print(sourceMessage);
        } catch (Exception e) {
            e.printStackTrace();
            throw new HexServerException("ProtoJsonUtils->toJson->errorMessage:proto序列化string异常");
        }
    }


    /**
     * @desc: String 转 Builder对象
     */
    private static <M extends Message.Builder> void jsonToBuilder(M targetBuilder, String json) {
        try {
            JsonFormat.parser().ignoringUnknownFields().merge(json, targetBuilder);
        } catch (Exception e) {
            e.printStackTrace();
            throw new HexServerException("ProtoJsonUtils->JsonToBuilder->errorMessage:json 转 Builder对象异常");
        }
    }

    /**
     * @desc: ProtoBean 转 JavaBean
     * messageOrBuilder protoBean
     * target javaBean 的 Class对象
     */
    public static <T, M extends MessageOrBuilder> T protoToBean(M messageOrBuilder, Class<T> target) {
        String protoJson = protoToJson(messageOrBuilder);
        Gson gson = getGson();
        return gson.fromJson(protoJson, target);
    }

    /**
     * @desc: ProtoBeanList 转 JavaBeanList
     */
    public static <T, M extends MessageOrBuilder> List<T> protoListToBeanList(List<M> messageOrBuilders, Class<T> target) {
        List<T> list = new ArrayList<>();
        for (M messageOrBuilder : messageOrBuilders) {
            T t = protoToBean(messageOrBuilder, target);
            list.add(t);
        }
        return list;
    }

    /**
     * @desc: ProtoBean 转 ProtoBean
     */
    public static <T extends Message, M extends Message> T protoToProto(M messageOrBuilder, Class<T> protoClass) {
        try {
            String protoJson = protoToJson(messageOrBuilder);

            Constructor<T> declaredConstructor = protoClass.getDeclaredConstructor((Class<?>[]) null);

            declaredConstructor.setAccessible(Boolean.TRUE);

            T instance = declaredConstructor.newInstance();

            Message.Builder builder = instance.toBuilder();

            jsonToBuilder(builder, protoJson);

            return (T) builder.build();
        } catch (Exception e) {
            e.printStackTrace();
            throw new HexServerException("ProtoJsonUtils->protoToProto->errorMessage:ProtoBean 转 ProtoBean 异常");
        }

    }

    /**
     * @desc: ProtoListBean 转 ProtoListBean
     */
    public static <T extends Message, M extends Message> List<T> protoListToProtoList(List<M> messageOrBuilders, Class<T> protoClass) {
        List<T> list = new ArrayList<>();
        for (M messageOrBuilder : messageOrBuilders) {
            T t = protoToProto(messageOrBuilder, protoClass);
            list.add(t);
        }
        return list;
    }


    /**
     * @desc: JavaBean 转 JavaBean  (单个对象)
     */
    public static <T, M> T beanToBean(M source, Class<T> target) {
        Gson gson = getGson();
        String json = gson.toJson(source);
        return gson.fromJson(json, target);
    }

    /**
     * @desc: JavaBeanList 转 JavaBeanList
     */
    public static <T> List<T> beanToBean(Collection<?> source, Class<T> target) {
        Gson gson = getGson();
        String json = gson.toJson(source);
        return gson.fromJson(json, new ParameterizedTypeImpl(target));
    }

    /**
     * @desc: javaBean 转 Json
     */
    public static String beanToJson(Object o) {
        Gson gson = getGson();
        return gson.toJson(o);
    }

    /**
     * @desc: json 转 JavaBean
     */
    public static <T> T jsonToBean(String jsonString, Class<T> clazz) {
        Gson gson = getGson();
        return gson.fromJson(jsonString, clazz);
    }

    public static <T> List<T> jsonToBeanList(String json, Class<T> clazz) {
        Type type = new ParameterizedTypeImpl(clazz);
        Gson gson = getGson();
        return gson.fromJson(json, type);
    }

    /**
     * @desc: JavaBean 转 Builder对象
     */

    public static <M extends Message.Builder, T> M beanToBuilder(T beanSource, Class<M> targetBuilderClass) {
        try {
            Constructor<M> declaredConstructor = targetBuilderClass.getDeclaredConstructor((Class<?>[]) null);

            declaredConstructor.setAccessible(Boolean.TRUE);

            M instance = declaredConstructor.newInstance();

            Gson gson = getGson();

            String json = gson.toJson(beanSource);

            jsonToBuilder(instance, json);

            return instance;
        } catch (Exception e) {
            e.printStackTrace();
            throw new HexServerException("ProtoJsonUtils->beanToBuilder->errorMessage:JavaBean 转 Builder对象异常");
        }
    }

    /**
     * @desc: javaBean 转 Proto
     */
    @SneakyThrows
    public static <M extends Message, T> M beanToProto(T bean, Class<M> targetProtoClass) {

        Constructor<M> declaredConstructor = targetProtoClass.getDeclaredConstructor((Class<?>[]) null);

        declaredConstructor.setAccessible(Boolean.TRUE);

        M instance = declaredConstructor.newInstance();

        Gson gson = getGson();

        String json = gson.toJson(bean);

        Message.Builder builder = instance.toBuilder();

        jsonToBuilder(builder, json);

        return (M) builder.build();
    }

    /**
     * @desc: javaBean 列表 转 ProtoBean 列表
     */
    public static <M, T extends Message> List<T> beanListToProtoList(Collection<M> beanSourceList, Class<T> protoBean) {
        try {
            List<T> resultList = new ArrayList<>();
            for (Object bean : beanSourceList) {

                Constructor<T> declaredConstructor = protoBean.getDeclaredConstructor((Class<?>[]) null);

                declaredConstructor.setAccessible(Boolean.TRUE);

                T instance = declaredConstructor.newInstance();

                Class<? extends Message.Builder> aClass = instance.toBuilder().getClass();

                Message.Builder builder = beanToBuilder(bean, aClass);

                resultList.add((T) builder.build());
            }
            return resultList;
        } catch (Exception e) {
            e.printStackTrace();
            throw new HexServerException("ProtoJsonUtils->beanListToProtoList->errorMessage:javaBean 列表 转 ProtoBean 列表异常");
        }
    }

    private static Gson getGson() {
        if (gson != null) {
            return gson;
        }
        gson = new GsonBuilder()
                .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter())
                .registerTypeAdapter(LocalDate.class, new LocalDateAdapter())
                .registerTypeAdapter(LazyStringList.class, new TypeAdapter<LazyStringList>() {

                    @Override
                    public void write(JsonWriter jsonWriter, LazyStringList strings) throws IOException {

                    }

                    @Override
                    public LazyStringList read(JsonReader in) throws IOException {
                        LazyStringList lazyStringList = new LazyStringArrayList();

                        in.beginArray();

                        while (in.hasNext()) {
                            lazyStringList.add(in.nextString());
                        }

                        in.endArray();

                        return lazyStringList;
                    }
                })
                .create();
        return gson;
    }

    public static final class LocalDateAdapter implements JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> {

        @Override
        public JsonElement serialize(LocalDate date, Type typeOfSrc, JsonSerializationContext context) {
            return new JsonPrimitive(date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        }

        @Override
        public LocalDate deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException {
            String timestamp = element.getAsJsonPrimitive().getAsString();
            return LocalDate.parse(timestamp, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        }
    }

    public static final class LocalDateTimeAdapter implements JsonSerializer<LocalDateTime>, JsonDeserializer<LocalDateTime> {

        @Override
        public JsonElement serialize(LocalDateTime date, Type typeOfSrc, JsonSerializationContext context) {
            return new JsonPrimitive(date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        }

        @Override
        public LocalDateTime deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException {
            String timestamp = element.getAsJsonPrimitive().getAsString();
            return LocalDateTime.parse(timestamp, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        }
    }

    private static class ParameterizedTypeImpl implements ParameterizedType {
        Class clazz;

        public ParameterizedTypeImpl(Class clz) {
            clazz = clz;
        }

        @Override
        public Type[] getActualTypeArguments() {
            return new Type[]{clazz};
        }

        @Override
        public Type getRawType() {
            return List.class;
        }

        @Override
        public Type getOwnerType() {
            return null;
        }
    }

}

自定义序列化适配器

上面有些是自定义的一些序列化器,可以根据自己的需求进行属性不同类型的转换,比如有时候前端传数组[1,2],后端保存成 "1,2"的方式存储到DB,然后查询的时候反过来,这时候就需要自定义序列化器

  1. 自定义序列化器
public static final class JsonListStringAdapter implements JsonSerializer<String>, JsonDeserializer<String> {
        @Override
        public JsonElement serialize(String string, Type typeOfSrc, JsonSerializationContext context) {
            JsonArray jsonArray = new JsonArray();
            String[] split = string.split(",");
            for (String s : split) {
                jsonArray.add(s);
            }
            return jsonArray;
        }

        @Override
        public String deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException {
            JsonArray jsonArray = element.getAsJsonArray();
            List<String> result = new ArrayList<>();
            for (JsonElement jsonElement : jsonArray) {
                result.add(jsonElement.getAsString());
            }
            return String.join(",", result);
        }
    }
  1. 如果只想某个字段使用 需要在属性上加 @JsonApapter
   /**
     * 适用渠道,多个用逗号隔开
     */
    @JsonAdapter(value = ProtoJsonUtils.JsonListStringAdapter.class)
    private String suitChannel;
  1. 所有字段都走这个序列化器,把这个注册进去
private static Gson getGson() {
        if (gson != null) {
            return gson;
        }
        gson = new GsonBuilder()
                .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter())
                .registerTypeAdapter(LocalDate.class, new LocalDateAdapter())
                .registerTypeAdapter(LazyStringList.class, new TypeAdapter<LazyStringList>() {

                    @Override
                    public void write(JsonWriter jsonWriter, LazyStringList strings) throws IOException {

                    }

                    @Override
                    public LazyStringList read(JsonReader in) throws IOException {
                        LazyStringList lazyStringList = new LazyStringArrayList();

                        in.beginArray();

                        while (in.hasNext()) {
                            lazyStringList.add(in.nextString());
                        }

                        in.endArray();

                        return lazyStringList;
                    }
                })
                .create();
        return gson;
    }

JsonSerializer

序列化接口 bean 序列化的时候需要用到

JsonDeserializer

反序列化接口 bean 返序列化的时候需要用到

JsonElement

承载序列化的字段类型

  • JsonArray 数组
  • JsonNull 空对象
  • JsonObject 对象
  • JsonPrimitive 原生字段