يعتبر Protobuff شيئًا رائعًا - تصف تركيبة واجهة برمجة التطبيقات الخاصة بك في ملف .proto ، الذي يتكون من البدائية ، ويمكنك إنشاء شفرة مصدر لأنظمة أساسية مختلفة - على سبيل المثال ، خادم في Java وعميل في C # ، أو العكس. نظرًا لأن هذا في الغالب هو واجهة برمجة تطبيقات للأنظمة الخارجية ، فمن المنطقي جعله غير قابل للتغيير ، وهذا الرمز نفسه يولد مولدًا قياسيًا لـ Java.
دعنا نفكر في مثال:
syntax = "proto2";
option java_multiple_files = true;
package org.example.api;
message Person { //
required int32 id = 1; // ,
required string name = 2; // ,
optional int32 age = 3; // ,
}
ونتيجة لذلك ، نحصل على فصل دراسي بالواجهة التالية:
public interface PersonOrBuilder extends
// @@protoc_insertion_point(interface_extends:org.example.api.Person)
com.google.protobuf.MessageOrBuilder {
boolean hasId();
int getId();
boolean hasName();
java.lang.String getName();
com.google.protobuf.ByteString getNameBytes();
boolean hasAge();
int getAge();
}
لاحظ أنه يتم استخدام المواد الأولية طوال الوقت (وهو أمر فعال للتسلسل والأداء). لكن المجال العمري اختياري ، لكن البدائي دائمًا له قيمة افتراضية. هذا ما يدهش حفنة من شفرة مرجعية سنقاتل معها.
Integer johnAge = john.hasAge() ? john.getAge() : null;
لكني أريد حقًا أن أكتب:
Integer johnAge = john.age().orElse(null); // age() - Optional<Integer>
يحتوي بروتوكول Protocol Buffers على آلية لتوسيع المكون الإضافي ويمكن كتابته بلغة Java ، وهو ما سنفعله.
ما هو البرنامج المساعد protobuf؟
هذا ملف قابل للتنفيذ يقرأ كائن PluginProtos.CodeGeneratorRequest من دفق الإدخال القياسي ، وينشئ PluginProtos.CodeGeneratorResponse من دفق الإدخال القياسي ويكتبه إلى دفق الإخراج القياسي.
public static void main(String[] args) throws IOException {
PluginProtos.CodeGeneratorRequest codeRequest = PluginProtos.CodeGeneratorRequest.parseFrom(System.in);
PluginProtos.CodeGeneratorResponse codeResponse;
try {
codeResponse = generate(codeRequest);
} catch (Exception e) {
codeResponse = PluginProtos.CodeGeneratorResponse.newBuilder()
.setError(e.getMessage())
.build();
}
codeResponse.writeTo(System.out);
}
دعونا نلقي نظرة فاحصة على ما يمكن أن نولد؟
يحتوي PluginProtos.CodeGeneratorResponse على مجموعة PluginProtos.CodeGeneratorResponse.File.
كل "ملف" هو فئة جديدة ننتجها بأنفسنا. إنها تتكون من:
String name; // , package
String content; //
String insertionPoint; //
أهم شيء لكتابة الإضافات - ليس علينا تجديد جميع الفئات - يمكننا استكمال الفئات الحالية باستخدام insertionPoint . إذا عدنا إلى الواجهة التي تم إنشاؤها أعلاه ، فسوف نرى هناك:
// @@protoc_insertion_point(interface_extends:org.example.api.Person)
في هذه الأماكن يمكننا إضافة رمزنا. وبالتالي ، لن نتمكن من إضافة قسم تعسفي من الفصل. سنبني على هذا. كيف يمكننا حل هذه المشكلة؟ يمكننا إنشاء واجهتنا الجديدة بطريقة افتراضية -
public interface PersonOptional extends PersonOrBuilder {
default Optional<Integer> age() {
return hasAge() ? Optional.of(getAge()) : Optional.empty();
}
}
وبالنسبة لفئة الشخص ، قم بإضافة تطبيق ليس فقط PersonOrBuilder ، ولكن أيضًا PersonOptional
كود لإنشاء الواجهة التي نحتاجها
@Builder
public class InterfaceWriter {
private static final Map<DescriptorProtos.FieldDescriptorProto.Type, Class<?>> typeToClassMap = ImmutableMap.<DescriptorProtos.FieldDescriptorProto.Type, Class<?>>builder()
.put(TYPE_DOUBLE, Double.class)
.put(TYPE_FLOAT, Float.class)
.put(TYPE_INT64, Long.class)
.put(TYPE_UINT64, Long.class)
.put(TYPE_INT32, Integer.class)
.put(TYPE_FIXED64, Long.class)
.put(TYPE_FIXED32, Integer.class)
.put(TYPE_BOOL, Boolean.class)
.put(TYPE_STRING, String.class)
.put(TYPE_UINT32, Integer.class)
.put(TYPE_SFIXED32, Integer.class)
.put(TYPE_SINT32, Integer.class)
.put(TYPE_SFIXED64, Long.class)
.put(TYPE_SINT64, Long.class)
.build();
private final String packageName;
private final String className;
private final List<DescriptorProtos.FieldDescriptorProto> fields;
public String getCode() {
List<MethodSpec> methods = fields.stream().map(field -> {
ClassName fieldClass;
if (typeToClassMap.containsKey(field.getType())) {
fieldClass = ClassName.get(typeToClassMap.get(field.getType()));
} else {
int lastIndexOf = StringUtils.lastIndexOf(field.getTypeName(), '.');
fieldClass = ClassName.get(field.getTypeName().substring(1, lastIndexOf), field.getTypeName().substring(lastIndexOf + 1));
}
return MethodSpec.methodBuilder(field.getName())
.addModifiers(Modifier.DEFAULT, Modifier.PUBLIC)
.returns(ParameterizedTypeName.get(ClassName.get(Optional.class), fieldClass))
.addStatement("return has$N() ? $T.of(get$N()) : $T.empty()", capitalize(field.getName()), Optional.class, capitalize(field.getName()), Optional.class)
.build();
}).collect(Collectors.toList());
TypeSpec generatedInterface = TypeSpec.interfaceBuilder(className + "Optional")
.addSuperinterface(ClassName.get(packageName, className + "OrBuilder"))
.addModifiers(Modifier.PUBLIC)
.addMethods(methods)
.build();
return JavaFile.builder(packageName, generatedInterface).build().toString();
}
}
الآن دعنا نرجع من البرنامج المساعد الرمز الذي يجب إنشاؤه
PluginProtos.CodeGeneratorResponse.File.newBuilder() // InsertionPoint,
.setName(String.format("%s/%sOptional.java", clazzPackage.replaceAll("\\.", "/"), clazzName))
.setContent(InterfaceWriter.builder().packageName(clazzPackage).className(clazzName).fields(optionalFields).build().getCode())
.build();
PluginProtos.CodeGeneratorResponse.File.newBuilder()
.setName(String.format("%s/%s.java", clazzPackage.replaceAll("\\.", "/"), clazzName))
.setInsertionPoint(String.format("message_implements:%s.%s", clazzPackage, clazzName)) // - message -
.setContent(String.format(" %s.%sOptional, ", clazzPackage, clazzName))
.build(),
كيف سنستخدم البرنامج المساعد الجديد؟ - عن طريق مخضرم ، قم بإضافة وتكوين البرنامج المساعد لدينا:
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<extensions>true</extensions>
<configuration>
<pluginId>java8</pluginId>
<protoSourceRoot>${basedir}/src/main/proto</protoSourceRoot>
<protocPlugins>
<protocPlugin>
<id>java8</id>
<groupId>org.example.protobuf</groupId>
<artifactId>optional-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<mainClass>org.example.proto2plugin.OptionalPlugin</mainClass>
</protocPlugin>
</protocPlugins>
</configuration>
</plugin>
ولكن يمكنك أيضًا تشغيله من وحدة التحكم - هناك ميزة واحدة لتشغيل ليس فقط المكون الإضافي الخاص بنا ، ولكن قبل ذلك تحتاج إلى الاتصال بمترجم جافا القياسي (ولكنك تحتاج إلى إنشاء ملف قابل للتنفيذ - protoc-gen-java8 (في حالتي ، مجرد برنامج نصي bash).
protoc -I=./src/main/resources/ --java_out=./src/main/java/ --java8_out=./src/main/java/ ./src/main/resources/example.proto
يمكن عرض رمز المصدر هنا .