SpringBoot+Maven+Protobuf+Redis

相关依赖:

        <springboot.version>2.0.2.RELEASE</springboot.version>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.31</version>
        </dependency>
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.6.1</version>
        </dependency>
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java-util</artifactId>
            <version>3.6.1</version>
        </dependency>
        
           <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.5.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
                    <!-- proto文件目录 -->
                    <protoSourceRoot>${project.basedir}/src/main/java/com/harrison/proto</protoSourceRoot>
                    <!-- 生成的Java文件目录 -->
                    <!--<outputDirectory>${project.build.directory}/generated-sources/protobuf</outputDirectory>-->
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>test-compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        

集成Protobuf

  • 配置<plugin>建议不要指定<outputDirectory>,用默认的就可以。一不留神把代码覆盖掉,就恭喜了。
  • 指定目录创建proto文件,User.proto
syntax = "proto3";//指定版本
option java_package = "com.harrison.protobuf";//制定生成java类包路径
option java_outer_classname = "UserModel";//制定生成java类名

message Users {

     repeated User users = 1;// proto没有list类型,对应repeated
     message User{
          string id = 1;
          string name = 2;
          string sex = 3;
     }
}
  • proto类型有一坑,int32 i = 0 和 bool b = false 时,转换成Json或者JavaBean时,为null。因为protobuf3没有required了int默认为0,bool默认为false,转换时取空。所以基本类型一律用string
  • maven import后,运行Plugins/protobuf/protobuf:compile,可以看到生成的MtMsgModel

ProtoBufUtil

根据需要,编写工具类,方便使用

    private static final Logger logger = LoggerFactory.getLogger(ProtoBufUtil.class);

    private static final  JsonFormat.Printer printer = JsonFormat.printer();

    private static final JsonFormat.Parser parser = JsonFormat.parser();

    /**
     * Proto 转化为Json
     * @param target
     * @return
     */
    public static String copyProtoBeanToJson(MessageOrBuilder target){
        try {
            return printer.print(target);
        } catch (InvalidProtocolBufferException e) {
            logger.error("ProtoBufUtil复制到Json异常",e);
            return null;
        }
    }

    /**
     * javabean转化为Proto
     * @param source
     * @param target
     * @param <T>
     * @return
     */
    public static <T extends Message> T copyJavaBeanToProtoBean(Object source, T.Builder target) {
        // javaBean 转换为Json
        String sourceStr = JSONUtil.bean2json(source);
        try {
            parser.merge(sourceStr, target);
            return (T) target.build();
        } catch (InvalidProtocolBufferException e) {
            logger.error("ProtoBufUtil复制到Proto异常",e);
        }
        return null;
    }


    /**
     * proto 转化为javabean
     * @param source
     * @param target
     * @param <T>
     * @return
     */
    public static <T> T copyProtoBeanToJavaBean(MessageOrBuilder source, Class<T> target){
        // protoBuf 转换为Json
        String soutceStr = copyProtoBeanToJson(source);
        return (T) JSONUtil.json2Object(soutceStr,target);
    }

    /**
     * 使用proto序列化javabean
     * @param source
     * @param target
     * @return
     */
    public static byte[] serializFromJavaBean(Object source,Message.Builder target){
        return copyJavaBeanToProtoBean(source,target).toByteArray();
    }

    /**
     * 使用proto反序列化javabean
     * @param source
     * @param parser
     * @param target
     * @param <T>
     * @return
     */
    public static <T> T deserializToJavaBean(byte[] source,Parser parser, Class<T> target) {
        try {
            return copyProtoBeanToJavaBean((MessageOrBuilder) parser.parseFrom(source),target);
        } catch (InvalidProtocolBufferException e) {
            logger.error("发序列化错误",e);
        }
        return null;
    }

集成Redis

这里使用springboot2的RedisTemplate,首先配置Serializer方式

    @Bean
    RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        //不使用默认的序列化
        template.setEnableDefaultSerializer(false);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }

这里有几个坑需要注意

  • 首先想到的应该是自定义序列化方式ProtocbufRedisSerializer
public class ProtocbufRedisSerializer<T> implements RedisSerializer<T> {

    private Class<T> type;

    public ProtocbufRedisSerializer(Class<T> type) {
        this.type = type;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        try {
            GeneratedMessageV3 gm = (GeneratedMessageV3) t;
            return gm.toByteArray();
        } catch (Exception ex) {
            throw new SerializationException("Cannot serialize", ex);
        }

    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes.length == 0) {
            return null;
        }
        try {
            Method method = type.getMethod("parseFrom", new Class[]{bytes.getClass()});
            return (T) method.invoke(type, new Object[]{bytes});
        } catch (Exception ex) {
            throw new SerializationException("Cannot deserialize", ex);
        }
    }

    public Class<T> getType() {
        return type;
    }

    public void setType(Class<T> type) {
        this.type = type;
    }
}

编码确实没问题,但解码就醉了。
Protobuf由byte[]解码到Bean需要指定type,这样的话RedisTemplate单例就没有办法是用了。每个ProtobufBean都写一个解码太冗余,不接受。
网上查了一圈,spring-data-redis 使用 protobuf进行序列化和反序列被这个博主点醒了。
既然有ProtoBufUtil工具类,每次直接push(byte[])然后再byte[]=pop(),对应序列化反序列化完事。
要注意的是 template.setEnableDefaultSerializer(false);,同时不要设置emplate.setValueSerializer(serializer);

再后面就是创建RedisUtil,开始使用喽。这里分享一个RedisUtil

经过ProtoBuf编码后放入redis,可以减少空间1~2倍,还是比较不错的。

    原文作者:Harrison
    原文地址: https://segmentfault.com/a/1190000020242708
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞