提示:Maven集成protobuf插件实现proto文件转换java类文件
前言
在网络通信中大家经常会接触到网络数据传输,然后不同的数据格式在传输中底层格式会有不同的却别。反之,不然以任何什么数据格式都是为了提高传输效率以及节省带宽消耗。常见的传输数据格式有fastjson(alibaba)、xml、protobuf(Google RPC)、hessian(Dubbo RPC)、jackson2(SpringMVC默认)、java原生序列化技术等。那这么多序列化方式我们到底选那种呢,这个就得根据具体的业务常见,框架来决定。如果对数据传输效率和空间有严格的要求就推荐大家用protobuf,如果比较折中方便上手可以建议用fastjson。下面我会从以上每种序列化技术实际占用空间大小来证明谁的压缩性能最好便于传输。
一、protobuf如何使用
本文以java开发环境为例,给大家分析如何使用google的protobuf序列化技术。
1、protobuf插件集成
<!--protobuf工具依赖包-->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.30.2</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.30.2</version>
</dependency>
<!--protobuf转换插件-->
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.32.1:exe:${os.detected.classifier}</pluginArtifact>
<clearOutputDirectory>false</clearOutputDirectory>
<!--
只要根据实际工程目录,修改一下两个配置。
protoSourceRoot:表示proto源文件所在的目录
outputDirectory:生成java类文件包所在的目录
-->
<protoSourceRoot>src/main/proto</protoSourceRoot>
<outputDirectory>src/main/java</outputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
2、工程目录
3、Test.proto文件分析
在使用protobuf序列化技术的时候前提大家都得先定义proto这个文件,然后通过maven protobuf插件逆向生成java文件,这样在java工程就能使用了。
// 显示声明使用proto3语法, 否则使用默认的proto2,类似于制定JDK版本
syntax = "proto3";
// 生成java文件的全包名
option java_package = "com.nb.proto.dto";
// 生成类的文件名,否则默认生成的类名为proto文件名的驼峰命名
option java_outer_classname = "TestProto";
// 定义的所有消息、枚举和服务生成对应的多个类文件,而不是以内部类的形式出现
option java_multiple_files = false;
// TestService
service TestService {
// test方法,格式为"方法名 请求参数 返回参数"
rpc test (Request) returns (Reply) {}
// test2
rpc test2 (Request) returns (Reply) {}
}
// 方法请求,包含用户名。最终会是TestProto类中的一个静态内部类。也是我们业务最终实际需要用到的类。
message Request {
string name = 1;
}
// 方法响应,包含响应的消息。最终会是TestProto类中的一个静态内部类。也是我们业务最终实际需要用到的类。
message Reply {
string message = 1;
}
4、触发生成java文件
在工程pom同级目录执行一下命令:
mvn clean compile
生成下面2个文件
由于以上两个文件内容太长,这里就不贴出来分析了。关于当前文章我们核心关注TestProto.java
文件。TestServiceGrpc
会涉及到google的grpc调用,这篇文章不做分析,本文核心分析序列化本身技术,对于grpc感兴趣的同学可以自行研究查阅相关资料。
5、简单使用
package com.nb.proto.test;
import com.alibaba.fastjson2.JSON;
import com.nb.proto.dto.TestProto;
/**
* @author duanshouzhi
* @create 2023-08-03 10:20
*/
public class ProtoTestMain {
public static void main(String[] args) {
TestProto.Request build = TestProto.Request.newBuilder().setName("666").build();
System.out.println(JSON.toJSONString(build));
}
}
打印出:
二、常见序列化技术
ISerializer
用于定义序列化基础的两个职责。一个序列化serialize、一个发序列化deserialize操作。
package com.serialized.demo;
import java.io.IOException;
/**
* @Auther: ShouZhi@Duan
* @Description: 序列化工具
*/
public interface ISerializer {
/**
* 序列化
*/
<T> byte[] serialize(T obj) throws IOException;
/**
* 反序列化
*/
<T> T deserialize(byte[] data);
}
1、Hessian
package com.serialized.demo;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* @Auther: ShouZhi@Duan
* @Description: Hessian序列、反序列方式
*/
public class HessianSerializer implements ISerializer {
@Override
public <T> byte[] serialize(T obj) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
HessianOutput hessianOutput = new HessianOutput(byteArrayOutputStream);
try {
hessianOutput.writeObject(obj);
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return new byte[0];
}
@Override
public <T> T deserialize(byte[] data) {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
HessianInput hessianInput = new HessianInput(byteArrayInputStream);
try {
return (T) hessianInput.readObject();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
2、JavaSerializer
package com.serialized.demo;
import java.io.*;
/**
* @Auther: ShouZhi@Duan
* @Description: JAVA序列、反序列方式
*/
public class JavaSerializer implements ISerializer {
/**
* 序列化
*/
@Override
public <T> byte[] serialize(T obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
//写到文件中
//ObjectOutputStream oosFile = new ObjectOutputStream(new FileOutputStream("E:/test/serial"));
oos.writeObject(obj);
byte[] bytes = baos.toByteArray();
return bytes;
}
/**
* 反序列化
*/
@Override
public <T> T deserialize(byte[] data) {
ByteArrayInputStream byteArrayOutputStream = new ByteArrayInputStream(data);
try {
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayOutputStream);
//从文件中读取
//ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(new File("E:/test/serial")));
return (T) objectInputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
3、XmlSerializer
package com.serialized.demo;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import lombok.extern.slf4j.Slf4j;
/**
* @Auther: ShouZhi@Duan
* @Description: XML序列、反序列方式
*/
@Slf4j
public class XmlSerializer implements ISerializer {
XStream stream;
public XmlSerializer() {
stream = new XStream(new DomDriver());
}
@Override
public <T> byte[] serialize(T obj) {
byte[] bytes = this.stream.toXML(obj).getBytes();
return bytes;
}
@Override
public <T> T deserialize(byte[] data) {
return (T) this.stream.fromXML(new String(data));
}
}
三、序列化空间占用对比实验
1、SerialTest
测试基准:同一个对象实例:DataDTO("java", 6, "浙江省杭州市滨江区")。分析最终序列化后的空间大小,谁的结果最小说明谁的数据压缩效果最好。
package com.serialized.demo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.protobuf.ByteString;
import com.serialized.dto.DataDTO;
import com.serialized.proto.DataDTOProto;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* @Auther: ShouZhi@Duan
* @Description: 各种序列技术性能测试
*/
@Slf4j
public class SerialTest {
private final static DataDTO dataDTO = new DataDTO("java", 6, "浙江省杭州市滨江区");
/**
* Java序列测试 233个字节
*/
@Test
public void javaSerialTest() throws IOException {
ISerializer serializer = new JavaSerializer();
//序列 233个字节
byte[] serialize = serializer.serialize(dataDTO);
log.info("JAVA序列后的对象大小:" + serialize.length + "字节");
//反序列
DataDTO deserialize = serializer.deserialize(serialize);
log.info("JAVA反序列得到的对象数据:" + deserialize);
log.info("JAVA序列测试完成");
}
/**
* Xml序列测试 144个字节
*/
@Test
public void xmlSerialTest() throws IOException {
ISerializer serializer = new XmlSerializer();
//序列 144个字节
byte[] serialize = serializer.serialize(dataDTO);
log.info("XML序列后的对象大小:" + serialize.length + "字节");
//反序列
DataDTO deserialize = serializer.deserialize(serialize);
log.info("XML反序列得到的对象数据:" + deserialize);
log.info("XML序列测试完成");
}
/**
* Hessian序列测试 97个字节
*/
@Test
public void hessianSerialTest() throws IOException {
ISerializer serializer = new HessianSerializer();
//序列 97个字节
byte[] serialize = serializer.serialize(dataDTO);
log.info("HESSIAN序列后的对象大小:" + serialize.length + "字节");
//反序列
DataDTO deserialize = serializer.deserialize(serialize);
log.info("HESSIAN反序列得到的对象数据:" + deserialize);
log.info("HESSIAN序列测试完成");
}
/**
* Jackson2序列测试 60个字节
*/
@Test
public void jacksoSerialTest() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectMapper o = new ObjectMapper();
o.writeValue(out, dataDTO);
log.info("JACKSON2序列后对象大小:" + out.toByteArray().length);
}
/**
* FastJson序列测试 40字节
*/
@Test
public void jsonSerialTest() {
System.out.println(JSON.toJSONString(dataDTO, SerializerFeature.BeanToArray));
byte[] bytes = JSON.toJSONBytes(dataDTO, SerializerFeature.BeanToArray);
log.info("FASTJSON序列后对象大小:" + bytes.length);
}
/**
* ProtoBuf序列测试 37个字节
*/
@Test
public void protoBufSerialTest() {
DataDTOProto.DataDTO dataDTO = DataDTOProto.DataDTO.newBuilder().setName("java").setAge(6).setAddr("浙江省杭州市滨江区").build();
ByteString bs = dataDTO.toByteString();
//序列 37个字节
byte[] bytes = bs.toByteArray();
log.info("PROTOC序列后的对象大小:" + bytes.length + "字节");
}
}
2、结果分析
- ProtoBuf序列测试
37
个字节 - FastJson序列测试
40
字节 - Jackson2序列测试
60
个字节 - Hessian序列测试
97
个字节 - Xml序列测试
144
个字节 - Java序列测试
233
个字节
从上面结果肉眼数字很容易看出来protobuf的压缩性能是最好的也是最小的空间占用、其次是fastjson。二者相差百分比不是很大。由此可见阿里的fastjson性能相对还是比较可靠的。
四、SpringBoot如何使用protobuf序列化
这个其实很简单,网上有很多相关资料
推荐:https://blog.csdn.net/zhou870498/article/details/130871074