Ao construir aplicações de rede de alta performance com Netty, o Protocol Buffers (Protobuf) surge como uma alternativa eficiente para serialização de dados. Esta abordagem oferece melhor desempenho e menor footprint de memória comparada ao JSON ou XML tradicional.
Definição dos Contratos de Mensagem
O primeiro passo consiste em criar os arquivos .proto que descrevem a estrutura das mensagens trocadas entre cliente e servidor.
Arquivo de Requisição
Crie o arquivo OrderRequest.proto dentro do diretório src/main/proto:
syntax = "proto2";
package com.example.nettymessages;
option java_package = "com.example.nettymessages";
option java_outer_classname = "OrderRequestProto";
message OrderRequest {
required int32 orderId = 1;
required string customerName = 2;
required string productCode = 3;
repeated string deliveryNotes = 4;
}
Arquivo de Resposta
Em seguida, defina OrderResponse.proto na mesma localização:
syntax = "proto2";
package com.example.nettymessages;
option java_package = "com.example.nettymessages";
option java_outer_classname = "OrderResponseProto";
message OrderResponse {
required int32 orderId = 1;
required int32 statusCode = 2;
repeated string feedbackMessages = 3;
}
Considerações sobre Sintaxe
Ao optar pela versão proto3, é necessário alterar a diretiva de sintaxe e remover os modificadores de obrigatoriedade:
syntax = "proto3";
No proto3, todos os campos são opcionais por padrão, eliminando a necessidade das palavras-chave required e optional. O operador repeated continua sendo utilizado para campos de lista.
Geração das Classes Java
Execute o compilador protobuf para gerar as classes Java correspondentes. Com Maven, utilize o plugin protobuf-maven-plugin:
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocExecutable>/usr/local/bin/protoc</protocExecutable>
<protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
<outputDirectory>${project.build.directory}/generated-sources</outputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
As classes geradas conterão toda a lógica de serialização, desserialização, builders e parsers necessários para o funcionamento correto. Caso apareçam avisos no IDE relacionados a estilos de código, isso não afeta a funcionalidade — são apenas sugestões de convenções de formatação.
Estrutura da Classe Gerada - OrderResponse
A classe OrderResponseProto gerada pelo compilador encapsula a definição da mensagem com todas as funcionalidades necessárias:
// Código gerado pelo compilador protobuf
// Fonte: OrderResponse.proto
package com.example.nettymessages;
public final class OrderResponseProto {
private OrderResponseProto() {}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistryLite registry) {
}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistry registry) {
registerAllExtensions(
(com.google.protobuf.ExtensionRegistryLite) registry);
}
public interface OrderResponseOrBuilder extends
com.google.protobuf.MessageOrBuilder {
boolean hasOrderId();
int getOrderId();
boolean hasStatusCode();
int getStatusCode();
java.util.List<String> getFeedbackMessagesList();
int getFeedbackMessagesCount();
String getFeedbackMessages(int index);
com.google.protobuf.ByteString getFeedbackMessagesBytes(int index);
}
public static final class OrderResponse extends
com.google.protobuf.GeneratedMessageV3 implements
OrderResponseOrBuilder {
private static final long serialVersionUID = 0L;
private OrderResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
super(builder);
}
private OrderResponse() {
orderId_ = 0;
statusCode_ = 0;
feedbackMessages_ = com.google.protobuf.LazyStringArrayList.EMPTY;
}
// Campos da mensagem
private int bitField0_;
public static final int ORDERID_FIELD_NUMBER = 1;
private int orderId_;
public boolean hasOrderId() {
return ((bitField0_ & 0x00000001) == 0x00000001);
}
public int getOrderId() {
return orderId_;
}
public static final int STATUSCODE_FIELD_NUMBER = 2;
private int statusCode_;
public boolean hasStatusCode() {
return ((bitField0_ & 0x00000002) == 0x00000002);
}
public int getStatusCode() {
return statusCode_;
}
public static final int FEEDBACKMESSAGES_FIELD_NUMBER = 3;
private com.google.protobuf.LazyStringList feedbackMessages_;
public com.google.protobuf.ProtocolStringList getFeedbackMessagesList() {
return feedbackMessages_;
}
public int getFeedbackMessagesCount() {
return feedbackMessages_.size();
}
public String getFeedbackMessages(int index) {
return feedbackMessages_.get(index);
}
public com.google.protobuf.ByteString getFeedbackMessagesBytes(int index) {
return feedbackMessages_.getByteString(index);
}
private byte memoizedIsInitialized = -1;
@Override
public final boolean isInitialized() {
byte isInitialized = memoizedIsInitialized;
if (isInitialized == 1) return true;
if (isInitialized == 0) return false;
if (!hasOrderId()) {
memoizedIsInitialized = 0;
return false;
}
if (!hasStatusCode()) {
memoizedIsInitialized = 0;
return false;
}
memoizedIsInitialized = 1;
return true;
}
// Builder pattern para construção das mensagens
public static final class Builder extends
com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
OrderResponseOrBuilder {
private int bitField0_;
private int orderId_;
private int statusCode_;
private com.google.protobuf.LazyStringList feedbackMessages_ =
com.google.protobuf.LazyStringArrayList.EMPTY;
public Builder setOrderId(int value) {
bitField0_ |= 0x00000001;
orderId_ = value;
onChanged();
return this;
}
public Builder setStatusCode(int value) {
bitField0_ |= 0x00000002;
statusCode_ = value;
onChanged();
return this;
}
private void ensureFeedbackMessagesIsMutable() {
if (!((bitField0_ & 0x00000004) == 0x00000004)) {
feedbackMessages_ = new com.google.protobuf.LazyStringArrayList(feedbackMessages_);
bitField0_ |= 0x00000004;
}
}
public Builder addFeedbackMessages(String value) {
if (value == null) {
throw new NullPointerException();
}
ensureFeedbackMessagesIsMutable();
feedbackMessages_.add(value);
onChanged();
return this;
}
public Builder clearFeedbackMessages() {
feedbackMessages_ = com.google.protobuf.LazyStringArrayList.EMPTY;
bitField0_ = (bitField0_ & ~0x00000004);
onChanged();
return this;
}
}
public static OrderResponse getDefaultInstance() {
return DEFAULT_INSTANCE;
}
private static final OrderResponse DEFAULT_INSTANCE;
static {
DEFAULT_INSTANCE = new OrderResponse();
}
public static OrderResponse parseFrom(java.nio.ByteBuffer data)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data);
}
public static OrderResponse parseFrom(byte[] data)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data);
}
@Deprecated
public static final com.google.protobuf.Parser<OrderResponse> PARSER =
new com.google.protobuf.AbstractParser<OrderResponse>() {
@Override
public OrderResponse parsePartialFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return new OrderResponse(input, extensionRegistry);
}
};
}
public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() {
return descriptor;
}
private static com.google.protobuf.Descriptors.FileDescriptor descriptor;
static {
// Metadados do descritor serializados
}
}
Uso Prático com Netty
Para integrar as mensagens Protobuf com Netty, configure os handlers de codec na pipeline do canal:
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
public class ProtobufChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast(new ProtobufVarint32FrameDecoder())
.addLast(new ProtobufDecoder(OrderResponseProto.OrderResponse.getDefaultInstance()))
.addLast(new ProtobufVarint32LengthFieldPrepender())
.addLast(new ProtobufEncoder())
.addLast(new BusinessHandler());
}
}
Construa uma mensagem de requisição utilizando o builder pattern fornecido pela classe gerada:
OrderRequestProto.OrderRequest request = OrderRequestProto.OrderRequest.newBuilder()
.setOrderId(1001)
.setCustomerName("Empresa XYZ")
.setProductCode("PRD-5678")
.addDeliveryNotes("Entrega na portaria principal")
.addDeliveryNotes("Contatar recepção antes da entrega")
.build();
As classes geradas pelo compilador protobuf podem ser utilizadas diretamente no projeto após ajustar o pacote e a estrutura de diretórios conforme a configuração do seu build. Certifique-se de incluir a dependência do Protobuf no arquivo de configuração do gerenciador de dependências.