java 集成 protobuf及二次压缩

一、创建springboot项目

1、创建proto文件目录 和 proto文件生成的java对象包

在这里插入图片描述

2、pom 中添加依赖包

## 版本
<grpc-version>1.6.1</grpc-version>
<protobuf-version>3.12.4</protobuf-version>        
## 依赖
<dependency>
	<groupId>com.google.protobuf</groupId>
	<artifactId>protobuf-java</artifactId>
	<version>${protobuf-version}</version>
</dependency>
<dependency>
	<groupId>com.google.protobuf</groupId>
	<artifactId>protobuf-java-util</artifactId>
	<version>${protobuf-version}</version>
</dependency>
<dependency>
	<groupId>com.googlecode.protobuf-java-format</groupId>
	<artifactId>protobuf-java-format</artifactId>
	<version>1.2</version>
</dependency>
<dependency>
	<groupId>io.grpc</groupId>
	<artifactId>grpc-netty</artifactId>
	<version>${grpc-version}</version>
</dependency>
<dependency>
	<groupId>io.grpc</groupId>
	<artifactId>grpc-protobuf</artifactId>
	<version>${grpc-version}</version>
</dependency>
<dependency>
	<groupId>io.grpc</groupId>
	<artifactId>grpc-stub</artifactId>
	<version>${grpc-version}</version>
</dependency>
<dependency>
	<groupId>io.protostuff</groupId>
	<artifactId>protostuff-core</artifactId>
	<version>1.7.4</version>
</dependency>
<dependency>
	<groupId>io.protostuff</groupId>
	<artifactId>protostuff-runtime</artifactId>
	<version>1.7.4</version>
</dependency>

3、编译proto文件,会自动生成class文件放到 proto包下

在这里插入图片描述

二、protobuf文件定义

1、proto3 对应各语言类型

.proto TypeNotesC++ TypeJava TypePython Type[2]Go Type
doubledoubledoublefloat*float64
floatfloatfloatfloat*float32
int32使用可变长度编码。编码负数的效率低 - 如果你的字段可能有负值,请改用 sint32int32intint*int32
int64使用可变长度编码。编码负数的效率低 - 如果你的字段可能有负值,请改用 sint64int64longint/long[3]*int64
uint32使用可变长度编码uint32int[1]int/long[3]*uint32
uint64使用可变长度编码uint64long[1]int/long[3]*uint64
sint32使用可变长度编码。有符号的 int 值。这些比常规 int32 对负数能更有效地编码int32intint*int32
sint64使用可变长度编码。有符号的 int 值。这些比常规 int64 对负数能更有效地编码int64longint/long[3]*int64
fixed32总是四个字节。如果值通常大于 228,则比 uint32 更有效。uint32int[1]int/long[3]*uint32
fixed64总是八个字节。如果值通常大于 256,则比 uint64 更有效。uint64long[1]int/long[3]*uint64
sfixed32总是四个字节int32intint*int32
sfixed64总是八个字节int64longint/long[3]*int64
boolboolbooleanbool*bool
string字符串必须始终包含 UTF-8 编码或 7 位 ASCII 文本stringStringstr/unicode[4]*string
bytes可以包含任意字节序列stringByteStringstr[]byte

2、定义proto文件

# 统一返回报文对象定义
syntax = "proto3";
option java_outer_classname = "ResultVo";
option java_package = "com.demo.proto";
import "google/protobuf/any.proto";

message ResultResponse {
  int32 code = 1;
  string msg = 2;
  google.protobuf.Any data = 3;# Any类型可代表proto对象
}
#返回业务Vo对象定义
syntax = "proto3";
option java_outer_classname = "AccountVo"; # java_outer_classname 的名字 要与 message 名字不同不然会编译不过
option java_package = "com.demo.proto";
message Account {
	//账号
	uint64 ID = 1;
	//名字
	string name = 2;
	//密码
	string password = 3;
	//临时
	string test = 4;
}

syntax = "proto3";
option java_outer_classname = "UserVo";
option java_package = "com.demo.proto";
import "Account.proto"; # 引入依赖对象
message User {
	Account user = 1;
}

三、自定义消息转换器HttpMessageConverter

转换器

package com.demo.configs.converter;

import com.demo.common.util.ZlibUtil;
import com.google.common.base.Stopwatch;
import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.Descriptors;
import com.google.protobuf.Message;
import net.jpountz.lz4.LZ4BlockOutputStream;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.xerial.snappy.SnappyOutputStream;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.Map;

public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<Message> {
    private Logger logger = LoggerFactory.getLogger(getClass());
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    public static final MediaType MEDIA_TYPE = new MediaType("application", "x-protobuf", DEFAULT_CHARSET);
    public static final MediaType MEDIA_TYPE_GZIP = new MediaType("application", "x-protobuf-zip", DEFAULT_CHARSET);
  
    private static final Map<Class<?>, Method> methodCache = new ConcurrentReferenceHashMap();
    /**
     * Construct a new instance.
     */
    public ProtobufHttpMessageConverter() {
        super(MEDIA_TYPE, MEDIA_TYPE_LZ4, MEDIA_TYPE_GZIP, MEDIA_TYPE_SNAPPY);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean canRead(final Class<?> clazz, final MediaType mediaType) {
        if(MEDIA_TYPE.isCompatibleWith(mediaType)
                ||MEDIA_TYPE_GZIP.isCompatibleWith(mediaType)){
            return true;
        }
        return false;
    }

    /**
     * {@inheritDoc}
     *  根据 mediaType判断需不需要走protobuf的转换
     */
    @Override
    public boolean canWrite(final Class<?> clazz, final MediaType mediaType) {
        if(MEDIA_TYPE.isCompatibleWith(mediaType)
                ||MEDIA_TYPE_GZIP.isCompatibleWith(mediaType)){
            return true;
        }
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Message readInternal(final Class<? extends Message> clazz, final HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
        Message.Builder builder = this.getMessageBuilder(clazz);
        // protobuf 序列化方式
        if (MEDIA_TYPE.isCompatibleWith(inputMessage.getHeaders().getContentType())) {
            builder.mergeFrom(inputMessage.getBody());
         //protobuf 序列化 + zlib 压缩   
        }else if(MEDIA_TYPE_GZIP.isCompatibleWith(inputMessage.getHeaders().getContentType())){
            ByteArrayOutputStream outputStream = null;
            try {
                long time = System.currentTimeMillis();
                InputStream inputStream = inputMessage.getBody();
                HttpHeaders httpHeaders = inputMessage.getHeaders();
                Descriptors.Descriptor descriptor =  builder.getDescriptorForType();
                Descriptors.FieldDescriptor fieldDescriptorLength = descriptor.findFieldByNumber(25);
                Descriptors.FieldDescriptor fieldDescriptorTime = descriptor.findFieldByNumber(26);
                Long length = httpHeaders.getContentLength();
                byte[] bytes = new byte[1024];
                outputStream = new ByteArrayOutputStream(length.intValue());
                int i =0;
                while ( (i = inputStream.read(bytes)) > 0){
                    outputStream.write(bytes,0,i);
                }
                byte[] data = ZlibUtil.uncompress(outputStream.toByteArray(),false);
                logger.info("压缩前的数据长度={}",data.length);
                //httpHeaders.add("uncompressLength",data.length+"");
                builder.setField(fieldDescriptorLength,data.length+"");
                builder.mergeFrom(data);
                long time2 = System.currentTimeMillis();
                logger.info("解压数据耗时---{}--长度={}",time2-time,length);
                //httpHeaders.add("uncompressTime",(time2-time)+"");
                builder.setField(fieldDescriptorTime,((time2-time)==0?1:(time2-time))+"");

            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                if(outputStream != null){
                    outputStream.close();
                }
            }

        }
        return builder.build();
    }

    private Message.Builder getMessageBuilder(Class<? extends Message> clazz) {
        try {
            Method method = (Method)methodCache.get(clazz);
            if (method == null) {
                method = clazz.getMethod("newBuilder");
                methodCache.put(clazz, method);
            }

            return (Message.Builder)method.invoke(clazz);
        } catch (Exception var3) {
            throw new HttpMessageConversionException("Invalid Protobuf Message type: no invocable newBuilder() method on " + clazz, var3);
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean supports(final Class<?> clazz) {
        // Should not be called, since we override canRead/canWrite.
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void writeInternal(Message message, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
        if (logger.isDebugEnabled()) {
            logger.info("Current type: {}", outputMessage.getHeaders().getContentType());
        }
        Stopwatch stopwatch = Stopwatch.createStarted();
        OutputStream stream = null;

        try {
            stream = outputMessage.getBody();
            if (MEDIA_TYPE.isCompatibleWith(outputMessage.getHeaders().getContentType())) {
                this.setProtoHeader(outputMessage, message);
                CodedOutputStream codedOutputStream = CodedOutputStream.newInstance(outputMessage.getBody());
                message.writeTo(codedOutputStream);
                codedOutputStream.flush();
                return;
            } else if (MEDIA_TYPE_GZIP.isCompatibleWith(outputMessage.getHeaders().getContentType())) {
                byte[] bytes = message.toByteArray();
                stream.write(ZlibUtil.compress(bytes,false));
                stream.flush();
            } else if (MEDIA_TYPE_LZ4.isCompatibleWith(outputMessage.getHeaders().getContentType())) {
                stream = new LZ4BlockOutputStream(stream);
            } else if (MEDIA_TYPE_SNAPPY.isCompatibleWith(outputMessage.getHeaders().getContentType())) {
                stream = new SnappyOutputStream(stream);
            } else {
               return;
            }

           message.writeTo(stream);
        } finally {
            IOUtils.closeQuietly(stream);
        }
        if (logger.isDebugEnabled()) {
            logger.info("Output spend {}", stopwatch.toString());
        }
    }
    private void setProtoHeader(HttpOutputMessage response, Message message) {
        response.getHeaders().set("X-Protobuf-Schema", message.getDescriptorForType().getFile().getName());
        response.getHeaders().set("X-Protobuf-Message", message.getDescriptorForType().getFullName());
    }
}

zlib解压缩工具

public class ZlibUtil {
    public static byte[] uncompress(byte[] input, boolean nowrap) throws IOException {
        Inflater inflater = new Inflater(nowrap);
        inflater.setInput(input);
        ByteArrayOutputStream baos = new ByteArrayOutputStream(input.length);
        try {
            byte[] buff = new byte[1024];
            while (!inflater.finished()) {
                int count = inflater.inflate(buff);
                /*if(count == 0){
                    break;
                }*/
                baos.write(buff, 0, count);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            baos.close();
        }
        inflater.end();
        byte[] output = baos.toByteArray();
        return output;
    }

    public static byte[] compress(byte[] data, boolean nowrap) throws IOException {
        byte[] output;
        Deflater compress = new Deflater(Deflater.BEST_COMPRESSION, nowrap);
        compress.reset();
        compress.setInput(data);
        compress.finish();
        ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length);
        try {
            byte[] buf = new byte[1024];
            while (!compress.finished()) {
                int i = compress.deflate(buf);
                bos.write(buf, 0, i);
            }
            output = bos.toByteArray();
        } catch (Exception e) {
            output = data;
            e.printStackTrace();
        } finally {
            bos.close();
        }
        compress.end();
        return output;
    }
}

四、Controller 层定义

 
   /**
     * 查询数据接口 ,请求接口时 ,content-type = 'application/x-protobuf-zip'
     * produces = "application/x-protobuf-zip" 自定义消息转换器会根据 这个mediaType类型判断,是否将返回信息封装成protobuf系列化    +zlib压缩
     * @param request
     * @param calResultQuery
     * @return
     */
    @PostMapping(value = "queryResult", produces = "application/x-protobuf-zip" )
    @ResponseBody
    public ResultVo.ResultResponse queryResult(HttpServletRequest request,@RequestBody CalResultQueryVO calResultQuery){
        ResultVo.ResultResponse.Builder resultResponse = ResultVo.ResultResponse.newBuilder();
        try {
            String globalId = (String) request.getAttribute("globalId");
            CalResultVO.CalResult calResult = parseProtoDataManager.parseResultData(calResultQuery,globalId);
            resultResponse.setData(Any.pack(calResult));
            resultResponse.setCode(Integer.valueOf(ErrorCode.SYS_CODE_200));
            resultResponse.setMsg("下载数据成功");
        }catch (Exception e){
            e.printStackTrace();
            resultResponse.setCode(Integer.valueOf(ErrorCode.SYS_CODE_500));
            resultResponse.setMsg("下载数据失败"+e.getMessage());
        }
        return resultResponse.build();
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值