相关依赖:
<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>
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; } }
根据需要,编写工具类,方便使用
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; }
这里使用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; }
这里有几个坑需要注意
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倍,还是比较不错的。