前面说过了pb的一些简单使用,继续说说关于pb中使用message扩展的情况。可以将一定范围内的字段标识号作为扩展所用,别人就可以在他们自己定义的.proto文件中为该消息添加新的字段了。
首先看看最基本的形式,这是我自定义的TestSimpleProtoBuf.proto文件
package client.message;
option java_package = "client.message";
option java_outer_classname = "SimpleProtoBuf";
message FigureInfo {
//ID不可为空
required int32 ID = 1;
// 100到200之间的作为扩展的标识号
extensions 100 to 200;
}
//对FigureInfo消息进行拓展,添加新的字段
extend FigureInfo {
required string email = 100;
optional int32 age = 101;
}
将可能需要拓展的消息中使用extension修饰符即可,表明后面的两个数字之间的数值可能会作为拓展字段的的标识号。对需要拓展的消息使用extend修饰符,在大括号里面添加新的字段即可。
将上次的测试代码稍加修改,一些解释的东西都在代码里,看代码吧
package client.test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import com.google.protobuf.ExtensionRegistry;
import client.message.SimpleProtoBuf;
import client.message.SimpleProtoBuf.FigureInfo.PhoneNumber;
public class TestSimpleProtoBuf {
public static void main(String[] args) throws IOException{
//按照自定义的proto创建一个FigureInfo
//序列化过程
//由编译器胜自动生成的message类是不可变的,一旦一个message对象创建后,就像Java中的String类一样是不可变的。创建一个message时,必须首先创建一个builder,设置必须的值后,再调用builder的build()方法
//builder的每个方法在消息修改后又返回builder,这个返回对象又可以调用其他方法。这种方式对于在同一行操作不同的方法提供了便利。
SimpleProtoBuf.FigureInfo.Builder figureInfoBuilder = SimpleProtoBuf.FigureInfo.newBuilder();
figureInfoBuilder.setID(123);
//拓展的消息字段需要使用setExtension()方法,第一个参数就是proto文件中的新添加的字段(类名.字段),第二个是其value
figureInfoBuilder.setExtension(SimpleProtoBuf.email, "wulilin_cool@163.com");
SimpleProtoBuf.FigureInfo figureInfoSend = figureInfoBuilder.build();
//-------------------发送方--------------------------------
ByteArrayOutputStream output = new ByteArrayOutputStream();
figureInfoSend.writeTo(output);
//-------------------接收方----------------------------------
byte[] byteArray = output.toByteArray();
ByteArrayInputStream input = new ByteArrayInputStream(byteArray);
//解析及反序列化
//由于proto文件中有拓展属性,需要使用ExtensionRegistry实例registry,调用registerAllExtensions()方法使SimpleProtoBuf类中的拓展数据关联到registry
ExtensionRegistry registry = ExtensionRegistry.newInstance();
SimpleProtoBuf.registerAllExtensions(registry);
//parseFrom()从一个特定的字节数组解析成消息,解析消息时,除了input流中,拓展的数据位于registry对象中
SimpleProtoBuf.FigureInfo figureInfoAccept = SimpleProtoBuf.FigureInfo.parseFrom(input, registry);
System.out.println(figureInfoAccept.getID());
//拓展消息中的内容需要通过getExtension()方法获取,参数就是proto文件中拓展字段(类名.字段)。
System.out.println(figureInfoAccept.getExtension(SimpleProtoBuf.email));
}
}
控制台信息:
123
waylyn
wulilin_cool@163.com
其实,我们在拓展消息时,不应该在老的.proto中修改,只需要重新新建一个.proto文件,将要拓展的message所在的文件名import进来,然后和前面说的一样添加新字段即可。看看分开的定义,这是TestSimpleProtoBuf.proto文件:
package client.message;
option java_package = "client.message";
option java_outer_classname = "SimpleProtoBuf";
message FigureInfo {
//ID不可为空
required int32 ID = 1;
// 100到200之间的作为扩展的标识号
extensions 100 to 200;
}
这是分开拓展的ExtendSimpleProtoBuf.proto文件:
package client.message;
option java_package = "client.message";
option java_outer_classname = "ExtendSimpleProtoBuf";
//对哪个message进行拓展,只需要引进那个message所在的.proto文件名即可
import "TestSimpleProtoBuf.proto";
//对FigureInfo消息进行拓展,添加新的字段
extend FigureInfo {
required string email = 100;
optional int32 age = 101;
}
Java测试代码也是稍作修改:
package client.test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import com.google.protobuf.ExtensionRegistry;
import client.message.ExtendSimpleProtoBuf;
import client.message.SimpleProtoBuf;
public class TestSimpleProtoBuf {
public static void main(String[] args) throws IOException{
//按照自定义的proto创建一个FigureInfo
//序列化过程
//由编译器胜自动生成的message类是不可变的,一旦一个message对象创建后,就像Java中的String类一样是不可变的。创建一个message时,必须首先创建一个builder,设置必须的值后,再调用builder的build()方法
//builder的每个方法在消息修改后又返回builder,这个返回对象又可以调用其他方法。这种方式对于在同一行操作不同的方法提供了便利。
SimpleProtoBuf.FigureInfo.Builder figureInfoBuilder = SimpleProtoBuf.FigureInfo.newBuilder();
figureInfoBuilder.setID(123);
//拓展的消息字段需要使用setExtension()方法,第一个参数就是proto文件中的新添加的字段(类名.字段),第二个是其value
//由于拓展的新字段在ExtendSimpleProtoBuf类中,修改成这样就行。
figureInfoBuilder.setExtension(ExtendSimpleProtoBuf.email, "wulilin_cool@163.com");
SimpleProtoBuf.FigureInfo figureInfoSend = figureInfoBuilder.build();
//-------------------发送方--------------------------------
ByteArrayOutputStream output = new ByteArrayOutputStream();
figureInfoSend.writeTo(output);
//-------------------接收方----------------------------------
byte[] byteArray = output.toByteArray();
ByteArrayInputStream input = new ByteArrayInputStream(byteArray);
//解析及反序列化
//由于proto文件中有拓展属性,需要使用ExtensionRegistry实例registry,调用registerAllExtensions()方法使ExtendSimpleProtoBuf类中的拓展数据关联到registry
ExtensionRegistry registry = ExtensionRegistry.newInstance();
ExtendSimpleProtoBuf.registerAllExtensions(registry);
//parseFrom()从一个特定的字节数组解析成消息,解析消息时,除了input流中,拓展的数据位于registry对象中
SimpleProtoBuf.FigureInfo figureInfoAccept = SimpleProtoBuf.FigureInfo.parseFrom(input, registry);
System.out.println(figureInfoAccept.getID());
//拓展消息中的内容需要通过getExtension()方法获取,参数就是proto文件中拓展字段(类名.字段)。
System.out.println(figureInfoAccept.getExtension(ExtendSimpleProtoBuf.email));
}
}
控制台信息:
123
waylyn
wulilin_cool@163.com
前一个例子单纯就是对老消息进行了扩展,我们稍稍复杂一点,在新消息中对老消息进行扩展。读者可千万别因为简单嫌啰嗦啊,嘿嘿。这时候其实有定义proto文件就有两种方法了。方法一:直接对extend中的message添加字段。把上面的proto文件略修改下,看看TestSimpleProtoBuf.proto文件:
package client.message;
option java_package = "client.message";
option java_outer_classname = "SimpleProtoBuf";
message FigureInfo {
//ID不可为空
required int32 ID = 1;
// 100到200之间的作为扩展的标识号
extensions 100 to 200;
}
新消息的ExtendSimpleProtoBuf.proto文件:
package client.message;
option java_package = "client.message";
option java_outer_classname = "ExtendSimpleProtoBuf";
//对哪个message进行拓展,只需要引进那个message所在的.proto文件名即可
import "TestSimpleProtoBuf.proto";
//定义新消息
message ExternalFigureInfo {
//对老消息FigureInfo消息进行拓展,添加新的字段
extend FigureInfo {
required string email = 100;
optional int32 age = 101;
}
}
测试的代码也只是略作修改:
package client.test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import com.google.protobuf.ExtensionRegistry;
import client.message.ExtendSimpleProtoBuf;
import client.message.SimpleProtoBuf;
public class TestSimpleProtoBuf {
public static void main(String[] args) throws IOException{
//按照自定义的proto创建一个FigureInfo
//序列化过程
//由编译器胜自动生成的message类是不可变的,一旦一个message对象创建后,就像Java中的String类一样是不可变的。创建一个message时,必须首先创建一个builder,设置必须的值后,再调用builder的build()方法
//builder的每个方法在消息修改后又返回builder,这个返回对象又可以调用其他方法。这种方式对于在同一行操作不同的方法提供了便利。
SimpleProtoBuf.FigureInfo.Builder figureInfoBuilder = SimpleProtoBuf.FigureInfo.newBuilder();
figureInfoBuilder.setID(123);
//拓展的消息字段需要使用setExtension()方法,第一个参数就是proto文件中的新添加的字段(类名.字段),第二个是其value
//由于拓展的新字段在ExternalFigureInfoExtendSimpleProtoBuf类中,修改成这样就行。
figureInfoBuilder.setExtension(ExtendSimpleProtoBuf.ExternalFigureInfo.email, "wulilin_cool@163.com");
SimpleProtoBuf.FigureInfo figureInfoSend = figureInfoBuilder.build();
//-------------------发送方--------------------------------
ByteArrayOutputStream output = new ByteArrayOutputStream();
figureInfoSend.writeTo(output);
//-------------------接收方----------------------------------
byte[] byteArray = output.toByteArray();
ByteArrayInputStream input = new ByteArrayInputStream(byteArray);
//解析及反序列化
//由于proto文件中有拓展属性,需要使用ExtensionRegistry实例registry,调用registerAllExtensions()方法使ExtendSimpleProtoBuf类中的拓展数据关联到registry
ExtensionRegistry registry = ExtensionRegistry.newInstance();
ExtendSimpleProtoBuf.registerAllExtensions(registry);
//parseFrom()从一个特定的字节数组解析成消息,解析消息时,除了input流中,拓展的数据位于registry对象中
SimpleProtoBuf.FigureInfo figureInfoAccept = SimpleProtoBuf.FigureInfo.parseFrom(input, registry);
System.out.println(figureInfoAccept.getID());
//拓展消息中的内容需要通过getExtension()方法获取,参数就是proto文件中拓展字段(类名.字段)。
String email = figureInfoAccept.getExtension(ExtendSimpleProtoBuf.ExternalFigureInfo.email);
System.out.println(email);
}
}
这是上个例子的另一种写法,我们把要扩展的字段不是放到老message中,而是放在新message中,然后将他们关联起来。 方法二:不是直接对extend中的message添加字段,而是将新字段放在新message中。看TestSimpleProtoBuf.proto文件:
package client.message;
option java_package = "client.message";
option java_outer_classname = "SimpleProtoBuf";
message FigureInfo {
//ID不可为空
required int32 ID = 1;
// 100到200之间的作为扩展的标识号
extensions 100 to 200;
}
ExtendSimpleProtoBuf.proto文件:
package client.message;
option java_package = "client.message";
option java_outer_classname = "ExtendSimpleProtoBuf";
//对哪个message进行拓展,只需要引进那个message所在的.proto文件名即可
import "TestSimpleProtoBuf.proto";
//定义新消息AddFigureInfoEmail是FigureInfo的扩展(扩展类型是AddFigureInfoEmail),扩展被定义为了AddFigureInfoEmail的一部分
message AddFigureInfoEmail {
//对老消息FigureInfo消息进行拓展,添加新的字段
extend FigureInfo {
//扩展是新消息的一部分,所以类型就是message名
optional AddFigureInfoEmail addFigureInfoEmail = 101;
}
//和前面例子不一样,添加的新字段放在新消息中(前面的例子是这样的,直接放在extend FigureInfo {required string email = 1;})
required string email = 1;
}
这样的话,测试代码也就会有点不一样了,Java代码如下:
package client.test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import com.google.protobuf.ExtensionRegistry;
import client.message.ExtendSimpleProtoBuf;
import client.message.ExtendSimpleProtoBuf.AddFigureInfoEmail;
import client.message.SimpleProtoBuf;
public class TestSimpleProtoBuf {
public static void main(String[] args) throws IOException{
//按照自定义的proto创建一个FigureInfo
//序列化过程
//由编译器胜自动生成的message类是不可变的,一旦一个message对象创建后,就像Java中的String类一样是不可变的。创建一个message时,必须首先创建一个builder,设置必须的值后,再调用builder的build()方法
//builder的每个方法在消息修改后又返回builder,这个返回对象又可以调用其他方法。这种方式对于在同一行操作不同的方法提供了便利。
SimpleProtoBuf.FigureInfo.Builder figureInfoBuilder = SimpleProtoBuf.FigureInfo.newBuilder();
figureInfoBuilder.setID(123);
//拓展的消息字段需要使用setExtension()方法,第一个参数就是proto文件中的新添加的字段(类名.字段),第二个是其value
//由于拓展的新字段在ExternalFigureInfoExtendSimpleProtoBuf类中,修改成这样就行。
ExtendSimpleProtoBuf.AddFigureInfoEmail.Builder addFigureInfoEmailBuilder = ExtendSimpleProtoBuf.AddFigureInfoEmail.newBuilder();
//addFigureInfoEmailBuilder.setExternalID(2014);
addFigureInfoEmailBuilder.setEmail("wulilin_cool@163.com");
figureInfoBuilder.setExtension(ExtendSimpleProtoBuf.AddFigureInfoEmail.addFigureInfoEmail, addFigureInfoEmailBuilder.build());
SimpleProtoBuf.FigureInfo figureInfoSend = figureInfoBuilder.build();
//-------------------发送方--------------------------------
ByteArrayOutputStream output = new ByteArrayOutputStream();
figureInfoSend.writeTo(output);
//-------------------接收方----------------------------------
byte[] byteArray = output.toByteArray();
ByteArrayInputStream input = new ByteArrayInputStream(byteArray);
//解析及反序列化
//由于proto文件中有拓展属性,需要使用ExtensionRegistry实例registry,调用registerAllExtensions()方法使ExtendSimpleProtoBuf类中的拓展数据关联到registry
ExtensionRegistry registry = ExtensionRegistry.newInstance();
ExtendSimpleProtoBuf.registerAllExtensions(registry);
//parseFrom()从一个特定的字节数组解析成消息,解析消息时,除了input流中,拓展的数据位于registry对象中
SimpleProtoBuf.FigureInfo figureInfoAccept = SimpleProtoBuf.FigureInfo.parseFrom(input, registry);
System.out.println(figureInfoAccept.getID());
//拓展消息中的内容需要通过getExtension()方法获取,参数就是proto文件中拓展字段(类名.字段)。
//当定义pb的时候,将拓展的字段放在外面的message中,这样可以直接使用extend 中的addFigureInfoEmail属性名获取新消息的数据
AddFigureInfoEmail ext = figureInfoAccept.getExtension(ExtendSimpleProtoBuf.AddFigureInfoEmail.addFigureInfoEmail);
System.out.println(ext.getEmail());
}
}
email: "wulilin_cool@163.com"
wulilin_cool@163.com
其实上面嵌套的两种写法都是可以的,但是本人更建议使用第二种方法,这样的话,当我们取属性值的时候不用每一个属性都调用一次getExtesion("属性名"),直接获取新消息对象object,通过object.getXX("属性名")获取属性数值,这样只需要调用一次getExtension()方法获取对象即可。还有一个好处就是项目有很搓人参与,要是大家都将拓展的字段直接放到老消息中,有可能几个人添加了同名字段,岂不是很混乱,所以还是将扩展的字段写到另一个消息中去。
接下来,我们再稍微写复杂一点,将枚举、嵌套消息和扩展一起使用。当然了,上面的定义新消息中拓展老消息其实就是一种最简单的嵌套和扩展一起使用的例子了。老样子,先看看TestSimpleProtoBuf.proto的定义:
package client.message;
option java_package = "client.message";
option java_outer_classname = "SimpleProtoBuf";
//枚举常量(以后可能会需要的消息都放到这里面)
enum FigureInfoMessageType {
//增加人物email消息是标识号101,拓展FigureInfo消息
ADD_FIGURE_INFO_EMAIL_TYPE = 101;
//增加人物address消息是标识号102,拓展FigureInfo消息
ADD_FIGURE_INFO_ADDRESS_TYPE = 102;
}
message FigureInfo {
//ID不可为空
required int32 ID = 1;
//FigureInfo 消息中嵌套枚举类型
optional FigureInfoMessageType figureInfoMessageType = 2;
// 100到200之间的作为扩展的标识号
extensions 100 to 200;
}
ExtendSimpleProtoBuf.proto文件内容:
package client.message;
option java_package = "client.message";
option java_outer_classname = "ExtendSimpleProtoBuf";
//对哪个message进行拓展,只需要引进那个message所在的.proto文件名即可
import "TestSimpleProtoBuf.proto";
//定义新消息AddFigureInfoEmail是FigureInfo的扩展(扩展类型是AddFigureInfoEmail),扩展被定义为了AddFigureInfoEmail的一部分
message AddFigureInfoEmail {
//对老消息FigureInfo消息进行拓展,添加新的字段
extend FigureInfo {
//将此消息和TestSimpleProtoBuf.proto文件中的enum中的增加email常量关联起来(即此字段的标识号就是enum中需要扩展消息的标识号)
optional AddFigureInfoEmail addFigureInfoEmail = 101;
}
//添加的新字段放在新消息中
required string email = 1;
}
Java的测试代码只是稍稍修改一丁点就行了。
package client.test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import com.google.protobuf.ExtensionRegistry;
import client.message.ExtendSimpleProtoBuf;
import client.message.SimpleProtoBuf;
public class TestSimpleProtoBuf {
public static void main(String[] args) throws IOException{
//先使用老消息对象设置一些数值
SimpleProtoBuf.FigureInfo.Builder figureInfoBuilder = SimpleProtoBuf.FigureInfo.newBuilder();
figureInfoBuilder.setID(123);
//设置老消息的2号标识号数值,value是其proto文件enum中的一个(定义proto文件时此字段的类型是enum),我们测试是使用添加email
figureInfoBuilder.setFigureInfoMessageType(SimpleProtoBuf.FigureInfoMessageType.ADD_FIGURE_INFO_EMAIL_TYPE);
//新消息对象设置一些数值
ExtendSimpleProtoBuf.AddFigureInfoEmail.Builder addFigureInfoEamilBuilder = ExtendSimpleProtoBuf.AddFigureInfoEmail.newBuilder();
addFigureInfoEamilBuilder.setEmail("wulilin_cool@163.com");
//拓展的消息字段需要使用setExtension()方法,第一个参数就是proto文件中的新添加的字段(类名.字段),第二个是其value,value是新对象设置的数值
//将新消息生成的对象绑定到老消息中
figureInfoBuilder.setExtension(ExtendSimpleProtoBuf.AddFigureInfoEmail.addFigureInfoEmail, addFigureInfoEamilBuilder.build());
SimpleProtoBuf.FigureInfo figureInfoSend = figureInfoBuilder.build();
//-------------------发送方--------------------------------
ByteArrayOutputStream output = new ByteArrayOutputStream();
figureInfoSend.writeTo(output);
//-------------------接收方----------------------------------
byte[] byteArray = output.toByteArray();
ByteArrayInputStream input = new ByteArrayInputStream(byteArray);
//解析及反序列化
//由于proto文件中有拓展属性,需要使用ExtensionRegistry实例registry,调用registerAllExtensions()方法使ExtendSimpleProtoBuf类中的拓展数据关联到registry
ExtensionRegistry registry = ExtensionRegistry.newInstance();
ExtendSimpleProtoBuf.registerAllExtensions(registry);
//parseFrom()从一个特定的字节数组解析成消息,解析消息时,除了input流中,拓展的数据位于registry对象中
SimpleProtoBuf.FigureInfo figureInfoAccept = SimpleProtoBuf.FigureInfo.parseFrom(input, registry);
System.out.println(figureInfoAccept.getID());
//拓展消息中的内容需要通过getExtension()方法获取,参数就是proto文件中拓展字段(类名.字段)。
ExtendSimpleProtoBuf.AddFigureInfoEmail ext = figureInfoAccept.getExtension(ExtendSimpleProtoBuf.AddFigureInfoEmail.addFigureInfoEmail);
System.out.println(ext.getEmail());
}
}
看下控制台信息:
123
"wulilin_cool@163.com"
PB在生成Java代码时,根据我们的定义将一个.proto文件生成一个制定名的最终类,而文件中的每一个message也是最终类的内部类,.proto中的每一个字段就是类中的静态属性。