因为工作原因,需要在websocket通信中传输protobuf格式的数据,所以了解了一下protobuf与spring boot的整合使用
是什么
一种与语言无关,与平台无关的可扩展机制,用于序列化结构化数据
使用
protobuf基本使用
从官网获取的基本使用方法
- 转换工具安装
为了将通用配置文件转换为特定的语言实现,需要在本地安装工具
推荐使用scoop快速安装
scoop install protobuf
- 文件转换
示例:在项目根目录执行下述命令:
protoc -I=proto --java_out=src/main/java proto/LineProto.proto
命令格式
protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/addressbook.proto
$SRC_DIR为proto文件放置的目录,$DST_DIR为类文件根目录
生成类的包名、类名在proto文件中指定
option java_package = "com.sayyi.proto";
option java_outer_classname = "MsgProtoClass";
与spring boot整合
源码地址,spring boot版本为1.5.10.RELEASE
- master: http代码
- feature/socket: websocket代码
http
参考spring boot官方提供的案例实现
- 添加依赖
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.9.1</version>
</dependency>
<dependency>
<groupId>com.googlecode.protobuf-java-format</groupId>
<artifactId>protobuf-java-format</artifactId>
<version>1.4</version>
</dependency>
- 配置数据转换器
/**
* 启用protobuf类型数据转换
* @return
*/
@Bean
ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
之后即可处理protobuf数据
如果想要返回protobuf数据,只要声明响应类型即可
@PostMapping(value = "/msgProtos", produces="application/x-protobuf;charset=UTF-8")
websocket
原生
仿照ProtobufHttpMessageConverter实现二进制数据的处理方法即可
package com.sayyi.socket;
import com.google.protobuf.ExtensionRegistry;
import com.google.protobuf.Message;
import org.springframework.util.FileCopyUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
public class ProtobufHelper {
/**
* 转换信息缓存
*/
private static final ConcurrentHashMap<Class<?>, Method> methodCache = new ConcurrentHashMap<>();
private final ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance();
public ProtobufHelper() {
}
public <T extends Message> byte[] toBytes(T message) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
FileCopyUtils.copy(message.toByteArray(), out);
return out.toByteArray();
}
@SuppressWarnings("unchecked")
public <T extends Message> T toObj(Class<T> targetClass, byte[] data) throws Exception {
Message.Builder builder = getMessageBuilder(targetClass);
builder.mergeFrom(new ByteArrayInputStream(data), this.extensionRegistry);
return (T) builder.build();
}
private static Message.Builder getMessageBuilder(Class<? extends Message> clazz) throws Exception {
Method method = methodCache.get(clazz);
if (method == null) {
method = clazz.getMethod("newBuilder");
methodCache.put(clazz, method);
}
return (Message.Builder) method.invoke(clazz);
}
}
stomp
最好不要使用stomp协议传输。stomp仅支持string类型的数据,后台可以通过自定义MessageConverter来实现二进制转对象,但是前端不能很好的处理二进制数据,只能通过String转换。这里提供参考org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter实现后端转换器
import com.google.protobuf.ExtensionRegistry;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.protobuf.ExtensionRegistryInitializer;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.AbstractMessageConverter;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.MimeType;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ConcurrentHashMap;
/**
* @ClassName: ProtobufMessageConverter
* @Description:
* @author: XC
* @date: 2019-10-24 17:51:26
*/
public class ProtobufMessageConverter extends AbstractMessageConverter {
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
public static final MediaType PROTOBUF = new MediaType("application", "x-protobuf", DEFAULT_CHARSET);
private static final ConcurrentHashMap<Class<?>, Method> methodCache = new ConcurrentHashMap<>();
private final ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance();
/**
* Construct a new instance.
*/
public ProtobufMessageConverter() {
this(null);
}
/**
* Construct a new instance with an {@link ExtensionRegistryInitializer}
* that allows the registration of message extensions.
*/
public ProtobufMessageConverter(ExtensionRegistryInitializer registryInitializer) {
super(PROTOBUF);
if (registryInitializer != null) {
registryInitializer.initializeExtensionRegistry(this.extensionRegistry);
}
}
@Override
protected boolean supports(Class<?> clazz) {
return com.google.protobuf.Message.class.isAssignableFrom(clazz);
}
@Override
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
MediaType contentType = getMediaType(getMimeType(message.getHeaders()));
Charset charset = contentType.getCharset();
if (charset == null) {
charset = DEFAULT_CHARSET;
}
try {
@SuppressWarnings("unchecked") com.google.protobuf.Message.Builder builder = getMessageBuilder((Class<? extends com.google.protobuf.Message>) targetClass);
Object payload = message.getPayload();
byte[] data;
if (payload instanceof byte[]) {
data = (byte[]) payload;
} else {
data = ((String) payload).getBytes(charset);
}
builder.mergeFrom(new ByteArrayInputStream(data), this.extensionRegistry);
return builder.build();
} catch (Exception ex) {
throw new HttpMessageNotReadableException("Could not read Protobuf message: " + ex.getMessage(), ex);
}
}
@Override
protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
com.google.protobuf.Message message = (com.google.protobuf.Message) payload;
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
FileCopyUtils.copy(message.toByteArray(), out);
} catch (IOException e) {
e.printStackTrace();
}
payload = out.toByteArray();
return payload;
}
private MediaType getMediaType(MimeType type) {
return type == null ?
PROTOBUF : new MediaType(type.getType(), type.getSubtype(), type.getCharset());
}
private static com.google.protobuf.Message.Builder getMessageBuilder(Class<? extends com.google.protobuf.Message> clazz) throws Exception {
Method method = methodCache.get(clazz);
if (method == null) {
method = clazz.getMethod("newBuilder");
methodCache.put(clazz, method);
}
return (com.google.protobuf.Message.Builder) method.invoke(clazz);
}
}