相关依赖:
<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 倍,还是比较不错的。