首頁 > 軟體

Springboot整合ProtoBuf的範例

2022-03-16 13:01:15

Springboot整合ProtoBuf

ProtoBuf是一種序列化和解析速度遠高於JSON和XML的資料格式,專案中使用了CouchBase作為快取伺服器,從資料庫中拿到資料後通過protobuf序列化後放入CouchBase作為快取,查詢資料的時候解壓並反序列化成資料物件,下面是ProtoBuf的具體使用方法

1、pom.xml引入相關依賴

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.5.0</version>
</dependency>
<dependency>
    <groupId>io.protostuff</groupId>
    <artifactId>protostuff-core</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>io.protostuff</groupId>
    <artifactId>protostuff-runtime</artifactId>
    <version>1.4.0</version>
</dependency>

2、新建序列化工具類ProtoBufUtil.java

package com.xrq.demo.utils; 
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.objenesis.Objenesis;
import org.springframework.objenesis.ObjenesisStd;
 
import io.protostuff.LinkedBuffer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeSchema;
 
/**
 * ProtoBufUtil 轉換工具類
 * 
 * @author XRQ
 *
 */
public class ProtoBufUtil {
    private static Logger log = LoggerFactory.getLogger(ProtoBufUtil.class);
    private static Map<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<Class<?>, Schema<?>>();
 
    private static Objenesis objenesis = new ObjenesisStd(true);
 
    @SuppressWarnings("unchecked")
    private static <T> Schema<T> getSchema(Class<T> cls) {
        Schema<T> schema = (Schema<T>) cachedSchema.get(cls);
        if (schema == null) {
            schema = RuntimeSchema.createFrom(cls);
            if (schema != null) {
                cachedSchema.put(cls, schema);
            }
        }
        return schema;
    }
 
    public ProtoBufUtil() {
    }
 
    @SuppressWarnings({ "unchecked" })
    public static <T> byte[] serializer(T obj) {
        Class<T> cls = (Class<T>) obj.getClass();
        LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
        try {
            Schema<T> schema = getSchema(cls);
            return ProtostuffIOUtil.toByteArray(obj, schema, buffer);
        } catch (Exception e) {
            log.error("protobuf序列化失敗");
            throw new IllegalStateException(e.getMessage(), e);
        } finally {
            buffer.clear();
        }
    }
 
    public static <T> T deserializer(byte[] bytes, Class<T> clazz) {
        try {
            T message = (T) objenesis.newInstance(clazz);
            Schema<T> schema = getSchema(clazz);
            ProtostuffIOUtil.mergeFrom(bytes, message, schema);
            return message;
        } catch (Exception e) {
            log.error("protobuf反序列化失敗");
            throw new IllegalStateException(e.getMessage(), e);
        }
    }
 
}

3、新建實體類User.java

注:重點是@Tag標籤需要按照順序往下排,如果需要新增欄位,只能接著往下排,不能改變已存在的標籤序號

package com.xrq.demo.bo; 
import java.io.Serializable;
import java.util.Date;  
import io.protostuff.Tag;
 
/**
 * 使用者資訊類
 * 
 * @ClassName: User
 * @author XRQ
 * @date 2019年4月30日
 */
public class User implements Serializable
{
    private static final long serialVersionUID = 1L;
 
    // 使用者ID
    @Tag(1)
    private int userId;
 
    // 使用者型別
    @Tag(2)
    private int userTypeId;
 
    // 使用者名稱 
    @Tag(3)
    private String userName;
 
    // 建立時間
    @Tag(4)
    private Date createDateTime; 
    public int getUserId()
    {
 
        return userId;
    }
 
    public void setUserId(int userId)
    {
 
        this.userId = userId;
    }
 
    public int getUserTypeId()
    {
 
        return userTypeId;
    }
 
    public void setUserTypeId(int userTypeId)
    {
 
        this.userTypeId = userTypeId;
    }
 
    public String getUserName()
    {
 
        return userName;
    }
 
    public void setUserName(String userName)
    {
 
        this.userName = userName;
    }
 
    public Date getCreateDateTime()
    {
 
        return createDateTime;
    }
 
    public void setCreateDateTime(Date createDateTime)
    {
 
        this.createDateTime = createDateTime;
    }
}

4、使用方式

User user = new User();
user.setUserId(1);
user.setUserTypeId(1);
user.setUserName("XRQ");
user.setCreateDateTime(new Date());
//序列化成ProtoBuf資料結構
byte[] userProtoObj= ProtoBufUtil.serializer(userInfo)
 
//ProtoBuf資料結構反序列化成User物件
User newUserObj = ProtoBufUtil.deserializer(userProtoObj, User.class))

ProtoBuf+Java+Springboot+IDEA應用 

什麼是Protobuf

1.Google Protocol Buffer( 簡稱 Protobuf) 是 Google 公司內部的混合語言資料標準;

2.Protocol Buffers 是一種輕便高效的結構化資料儲存格式,可以用於結構化資料序列化,或者說序列化。它很適合做資料儲存或 RPC 資料交換格式。可用於通訊協定、資料儲存等領域的語言無關、平臺無關、可延伸的序列化結構資料格式;

3.形式為.proto結尾的檔案;

應用環境

近期接觸公司的網約車介面對接專案,第三方公司限定了介面的資料用protobuf格式序列化後,通過AES128加密後傳輸。

開發環境

Java+Spring boot+IDEA+Windows

新建Spring boot專案 在IDEA開發工具中安裝protobuf外掛

新增設定maven依賴(可能一開始自己的專案中存在固有的設定,不要刪除,在對應的地方新增下面的設定即可,不需要修改)

<dependency>
	<groupId>io.protostuff</groupId>
	<artifactId>protostuff-runtime</artifactId>
	<version>1.4.0</version>
</dependency>
<plugin>
	<groupId>org.xolstice.maven.plugins</groupId>
	<artifactId>protobuf-maven-plugin</artifactId>
	<version>0.5.0</version>
	<configuration>
	<protocArtifact>
		com.google.protobuf:protoc:3.1.0:exe:${os.detected.classifier}
	</protocArtifact>
	<pluginId>grpc-java</pluginId>
	</configuration>
	<executions>
		<execution>
			<goals>
				<goal>compile</goal>
                <goal>compile-custom</goal>
            </goals>
        </execution>
    </executions>
</plugin>

寫專案對應的.proto檔案(這裡貼一部分程式碼)

// 版本號
syntax = "proto2";
// 打包路徑
option java_package="XXX1";
// Java檔名
option java_outer_classname="XXX2";
// 屬性資訊
message BaseInfo {
	// 公司標識
	required string CompanyId       = 1;
	// 公司名稱
	required string CompanyName     = 2;
    // 操作標識
	required uint32 Flag            = 3;
	// 更新時間
	required uint64 UpdateTime      = 4;
}

.proto檔案存在專案路徑

生成.java檔案方式(點選專案中的.proto檔案,找到對應的maven依賴,雙擊標識2對應的protobuf:compile檔案)

執行成功之後在對應路徑下檢視生成的.java檔案

生成的Java檔案即可使用(如果在業務中.proto檔案很大,生成的Java大小超出IDEA預設的2.5M,生成的Java檔案將被IDEA誤認為不是Java檔案,導致生成的Java檔案不可使用,這時需要修改IDEA的組態檔,將預設的大小修改,重啟IDEA即可) 進行資料序列化

List<BaseInfo> baseInfoList = baseInfoMapper.selectAll();
XXX2.OTIpcList.Builder listBuilder = XXX2.OTIpcList.newBuilder();
XXX2.OTIpc.Builder otipcBuilder = XXX2.OTIpc.newBuilder();
otipcBuilder.setCompanyId(ProjectConstant.COMPANY_ID);
otipcBuilder.setSource(ProjectConstant.COMPANY_SOURCE);
otipcBuilder.setIPCType(OTIpcDef.IpcType.baseInfoCompany);
for(int i=0;i<baseInfoList .size();i++){
	try{
		XXX2.BaseInfo.Builder baseInfoBuilder = XXX2.BaseInfo.newBuilder();
		baseInfoBuilder .setCompanyId(baseInfoList .get(i).getCompanyid());
		baseInfoBuilder .setCompanyName(baseInfoList .get(i).getCompanyname());
		baseInfoBuilder .setFlag(baseInfoList .get(i).getFlag());
		baseInfoBuilder .setUpdateTime(baseInfoList .get(i).getUpdatetime());
		for(int j=0;j<10;j++) {
			otipcBuilder.addBaseInfo(baseInfoBuilder .build());
		}
	}
	catch (Exception e){
		LoggerUtils.info(getClass(),e.getMessage());
	}
}
listBuilder.addOtpic(otipcBuilder);

進行資料AES128位元加密

public static String sendScreate(OTIpcDef.OTIpcList.Builder listBuilder) {
        String screateKeyString = getSecretKey();
        String screateKey = screateKeyString.split(";")[1];
        String screateValue = screateKeyString.split(";")[0];
        OTIpcDef.OTIpcList list = listBuilder.build();
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        try {
            list.writeTo(output);
        } catch (IOException e) {
            e.printStackTrace();
        }
        byte[] data = null;
        try{
            byte[] keyByte = new byte[screateValue.length()/2];
            for(int j=0;j<screateValue.length()/2;j++) {
                byte b = (byte) ((Integer.valueOf(String.valueOf(screateValue.charAt(j*2)), 16) << 4) |
                        Integer.valueOf(String.valueOf(screateValue.charAt(j*2+1)), 16));
                keyByte[j] = b;
		}
		Key secretKey= new SecretKeySpec(keyByte,"AES");
		Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
		Cipher cipher= Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
		cipher.init(Cipher.ENCRYPT_MODE, secretKey);
		data = cipher.doFinal(output.toByteArray());
		sendPostJSON(screateKey, data);
		return "success";
	} catch(Exception e){
		e.printStackTrace();
	}
	return null;
}

小結一下:經驗證,Protobuf 序列化相比XML,JSON效能更好,在Protobuf 官網看到在建立物件,將物件序列化為記憶體中的位元組序列,然後再反序列化的整個過程中相比其他相似技術的效能測試結果圖,但是在通用性上會存在侷限性,功能相對簡單,不適合用來描述資料結構。

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com