相关依赖:
<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 = "UsersModel";//制定生成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,可以看到生成的UsersModel
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(GeneratedMessageV3 target){
try {
return printer.print(target);
} catch (InvalidProtocolBufferException e) {
logger.error("ProtoBufUtil复制到Json异常",e);
return null;
}
}
/**
* javabean转化为Proto
* @param <T>
* @param source
* @param target
* @return
*/
public static <T extends GeneratedMessageV3> 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(GeneratedMessageV3 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,GeneratedMessageV3.Builder target){
GeneratedMessageV3 messageV3 = copyJavaBeanToProtoBean(source,target);
if (null != messageV3){
return messageV3.toByteArray();
}
return new byte[0];
}
/**
* 使用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((GeneratedMessageV3) 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倍,还是比较不错的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。