protobuff-java 替代品 prorostuff

陈子昂發表於2020-03-12

網路主題

傳送方需要將類物件轉換成二進位制壓入專案自定義資料結構,接收方接收到資料後,需要將二進位制轉換成類物件。
專案接收方透過訊息號和回執 ID,可以知道該訊息如何去處理。
prorostuff 相比.proto 檔案,不用先轉換和編譯.proto 檔案,不需要做一層中間檔案的轉換。這層中間檔案經常會和版本嚴格匹配時出現格式語法錯誤。
Pom 如下:

<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>

netty 的編碼類

netty 只用 4 是 Java 網路的一個主流方式
這裡需要透過繼承 Netty4MessageToByteEncoder Message 是 Bean 物件,根據自己專案定製來的

public class MessageEncoder extends MessageToByteEncoder<NettyMessage>{
   @Override
    protected void encode(ChannelHandlerContext ctx, NettyMessage msg, ByteBuf out) throws Exception {
        //ProrostuffSerialize提供了序列化方式。
        out.writeBytes(ProrostuffSerialize.serialize(msg));
    }
}

關於對 ProrostuffSerialize 實現,本身還是建立有序快取空間,schema 流式,提供正反序列化方式。

import com.dyuproject.protostuff.LinkedBuffer;

下面的 ProtostuffIOUtil 等都是這個包下面的。

public class ProrostuffSerialize{

    private static class SerializeObject{
        private Object target;
    }

    @SuppressWarnings("unchecked")
    public static byte[] serialize(Object object) {
        SerializeObject serializeObject = new SerializeObject();
        serializeObject .target = object;
        Class<serializeObject > serializeObjectClass = (Class<serializeObject >) serializeObject.getClass();
        LinkedBuffer buffer= LinkedBuffer.allocate(1024 * 4);
        try {
            Schema<serializeObject > schema = RuntimeSchema.getSchema(serializeObjectClass );
            return ProtostuffIOUtil.toByteArray(serializeObject , schema, buffer);
        } catch (Exception e) {
            throw new IllegalStateException(e.getMessage(), e);
        } finally {
            linkedBuffer.clear();
        }
    }


    //clazz泛型是Bean物件Message
    @SuppressWarnings("unchecked")
    public static <T> T deserialize(byte[] data, Class<T> clazz) {
        try {
            //建立SerializeObject的schema物件
            Schema<SerializeObject> schema = RuntimeSchema.getSchema(SerializeObject.class);
            SerializeObject serializeObject= schema.newMessage();
            ProtostuffIOUtil.mergeFrom(data, serializeObject, schema);
            return (T) serializeObject.target;
        } catch (Exception e) {
            log.error("not found schema obj");
            throw new IllegalStateException(e.getMessage(), e);
        }
    }

   public static <T> byte[] serializeList(List<T> objList) {
        if (CollectionUtils.isEmpty(objList)) {
            log.error("objList is empty");
            throw new RuntimeException("Failed to serializer");
        }
        @SuppressWarnings("unchecked") Schema<T> schema =
                (Schema<T>) RuntimeSchema.getSchema(objList.get(0).getClass());
        LinkedBuffer buffer = LinkedBuffer.allocate(1024 * 1024);
        byte[] bytes = null;
        ByteArrayOutputStream outputStream= null;
        try {
            outputStream= new ByteArrayOutputStream();
            ProtostuffIOUtil.writeListTo(outputStream, objList, schema, buffer);
            bytes = outputStream.toByteArray();
        } catch (Exception e) {
            log.error("Failed to serializer, objList={}", objList);
            throw new RuntimeException("Failed to serializer");
        } finally {
            buffer.clear();
            outputStream.close();
        }
        return bytes;
       }
  }

serialize 時候,使用這個 ProtobufIOUtil.toByteArray(T message, Schema schema, LinkedBuffer buffer) 壓到不同資料型別,預設是 byte[]
想變成字串在外面包一層 new String(ProtobufIOUtil.toByteArray()) 就行。
上面的反序列化陣列就不寫了。

netty 的解碼類

LengthFieldBasedFrameDecoder 繼承自 MessageToByteDecoder,也是使用 netty 對 Message 進行自行處理分流和處理分包。

public class MessageDecoder extends LengthFieldBasedFrameDecoder{

    public MessageDecoder(int maxLength, int offset, int lengthFieldLength) {
        super(maxLength, offset, lengthFieldLength);
    }

    @Override
    public  Object decode(ChannelHandlerContext ctx, ByteBuf input) throws Exception {
        try {
           //要使用readBytes防止會讀取不到,需要先給他申請可讀部分的空間,這塊是用隱式指標進行位移。
            byte[] dstBytes = new byte[input.readableBytes()];
           //讀取快取區接收到剩餘部分二進位制
            input.readBytes(dstBytes,0,input.readableBytes());
           //接收到Message物件
            return  SerializeUtil.deserialize(dstBytes, Message.class);
        } catch (Exception e) {
            log.error("exception when decoding: {}",e);
            return null;
        }
    }
}

然後把編碼器和解碼器新增到 nettyOptions 管道容器的地方

@Component
public class ChannelInitializer extends ChannelInitializer<SocketChannel> {

    //自定義Handler
    @Autowired
    ChannelHandler channelHandler;

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline channel= socketChannel.pipeline();
        channel.addLast("idleStateHandler",
                new IdleStateHandler(5, 0, 0, TimeUnit.MINUTES));
        //字串編解碼器
         channel.addLast("encoder", new NettyMessageEncoder());
        channel.addLast("decoder",new MessageDecoder(1024 * 1024, 4, 4));
        pipeline.addLast("channelHandler", ChannelHandler);
    }
}

施工中

相關文章