手写 Protobuf —— Java 代码生成

手写 Protobuf ------ Java 代码生成

在上一集手写 protobuf ------ 词法解析与语法解析完成了.proto 文件的词法分析和语法分析后,我们获得了一个抽象语法树(AST)。现在需要将这个 AST 转换为对应的 Java 代码。

代码生成器架构

核心组件

go 复制代码
type JavaProtoc struct {
    JavaOutput  string         // Java 文件输出目录
    PackageName string         // 包名
    ProtoName   string         // proto 文件名
    Protoc      *protoc.Protoc // AST 根节点
}

生成流程

  1. 生成外部类(Outer Class)
  2. 生成消息类(Message Class)
  3. 生成字段声明和访问器
  4. 生成序列化/反序列化方法

详细实现

外部类生成

go 复制代码
func (jp *JavaProtoc) generateOuterClass(innerStr strings.Builder) string {
    var fileStr strings.Builder
    // 生成包声明
    fileStr.WriteString(fmt.Sprintf("package %s;\n\n", jp.PackageName))
    
    // 生成类声明
    fileStr.WriteString("public final class " + toCamelCase(jp.ProtoName, true) + " {\n")
    fileStr.WriteString(innerStr.String())
    fileStr.WriteString("}\n")
    
    return fileStr.String()
}

消息类生成

go 复制代码
func (jp *JavaProtoc) generateMessageClass(msg *protoc.Message, inner bool) string {
    var builder strings.Builder
    
    // 生成类声明
    builder.WriteString(fmt.Sprintf("public final static class %s {\n", 
        toCamelCase(msg.Name, true)))
    
    // 生成字段
    for _, field := range msg.Fields {
        builder.WriteString(generateFieldDeclaration(field))
    }
    
    // 生成构造函数
    builder.WriteString(generateConstructor(msg))
    
    // 生成 getter/setter
    for _, field := range msg.Fields {
        builder.WriteString(generateGetterAndSetter(field))
    }
    
    // 生成序列化方法
    builder.WriteString(jp.generateToByteArray(msg))
    
    // 生成反序列化方法
    builder.WriteString(jp.generateParseFrom(msg))
    
    builder.WriteString("}\n")
    return builder.String()
}

字段处理

字段声明
go 复制代码
func generateFieldDeclaration(field *protoc.Field) string {
    javaType := toJavaType(field)
    return fmt.Sprintf("    private %s %s;\n", 
        javaType, toCamelCase(field.Name, false))
}
Getter/Setter
go 复制代码
func generateGetterAndSetter(field *protoc.Field) string {
    javaType := toJavaType(field)
    fieldName := toCamelCase(field.Name, true)
    
    return fmt.Sprintf(`
    public %s get%s() {
        return this.%s;
    }
    
    public void set%s(%s %s) {
        this.%s = %s;
    }`,
        javaType, fieldName, 
        toCamelCase(field.Name, false),
        fieldName, javaType, 
        toCamelCase(field.Name, false),
        toCamelCase(field.Name, false),
        toCamelCase(field.Name, false))
}

枚举类生成

让我来补充枚举类生成的实现部分:

枚举类生成

在 Protobuf 中,枚举是一种特殊的类型,它允许你为一个字段指定一组命名常量。在 Java 中,我们需要将其转换为 Java 的 enum 类型。

go 复制代码
func (jp *JavaProtoc) generateEnum(enum *protoc.Enum) string {
    className := toCamelCase(enum.Name, true)
    var builder strings.Builder

    // 生成枚举类声明
    builder.WriteString(fmt.Sprintf("public enum %s {\n", className))

    // 生成枚举值
    for i, value := range enum.Values {
        if i > 0 {
            builder.WriteString(",\n")
        }
        builder.WriteString(fmt.Sprintf("    %s(%d)", 
            strings.ToUpper(value.Name), value.Value))
    }
    builder.WriteString(";\n\n")

    // 生成值字段和构造函数
    builder.WriteString("    private final int value;\n\n")
    builder.WriteString(fmt.Sprintf("    %s(int value) {\n", className))
    builder.WriteString("        this.value = value;\n    }\n\n")

    // 生成获取数值的方法
    builder.WriteString("    public int getNumber() {\n")
    builder.WriteString("        return value;\n")
    builder.WriteString("    }\n")

    // 生成通过数值获取枚举实例的方法
    builder.WriteString(fmt.Sprintf("    public static %s forNumber(int value) {\n", className))
    builder.WriteString(fmt.Sprintf("        for (%s e : values()) {\n", className))
    builder.WriteString("            if (e.value == value) {\n")
    builder.WriteString("                return e;\n")
    builder.WriteString("            }\n")
    builder.WriteString("        }\n")
    builder.WriteString("        return null;\n")
    builder.WriteString("    }\n")

    builder.WriteString("}\n")
    return builder.String()
}

生成的 Java 枚举类具有以下特点:

  1. 每个枚举值都关联一个整数值

    java 复制代码
    public enum UserType {
        UNKNOWN(0),
        ADMIN(1),
        GUEST(2);
        // ...
    }
  2. 提供值的访问方法

    java 复制代码
    public int getNumber() {
        return value;
    }
  3. 支持通过值查找枚举实例

    java 复制代码
    public static UserType forNumber(int value) {
        for (UserType e : values()) {
            if (e.value == value) {
                return e;
            }
        }
        return null;
    }
  4. 遵循 Protobuf 的命名约定

    • 枚举值使用大写
    • 保持原始的数值映射
    • 支持不连续的枚举值

这样的实现确保了:

  • 与 Protobuf 规范的兼容性
  • 类型安全的枚举使用
  • 双向的值转换支持
  • 良好的代码可读性

特殊类型处理

OneOf 字段
go 复制代码
func generateOneOf(oneOf *protoc.OneOf) string {
    var builder strings.Builder
    
    // 生成字段
    builder.WriteString(fmt.Sprintf("    private Object %s;\n", 
        toCamelCase(oneOf.Name, false)))
    builder.WriteString(fmt.Sprintf("    private int %sCase = 0;\n", 
        toCamelCase(oneOf.Name, false)))
    
    // 生成 Case 枚举
    builder.WriteString(generateOneOfEnum(oneOf))
    
    // 生成访问器
    for _, field := range oneOf.Fields {
        builder.WriteString(generateOneOfAccessors(oneOf, field))
    }
    
    return builder.String()
}

让我们继续完善 GeneratedMessage 和 Any 类型的实现。

GeneratedMessage 实现

GeneratedMessage 是所有生成的消息类的基类,它提供了基本的序列化和反序列化功能。

java 复制代码
public abstract class GeneratedMessage {
    // 编码解码方法
}

Any 类型实现

Any中有两个字段

  • typeUrl 是用来保证反序列化时,不会反序列化成错误的类型
  • value 就是序列化后的值

具体实现可以查看下面的代码

java 复制代码
package com.protoc.qiu;

public class Any {
    private String typeUrl;
    private byte[] value;

    public Any() {}

    public String getTypeUrl() {
        return typeUrl;
    }

    public void setTypeUrl(String typeUrl) {
        this.typeUrl = typeUrl;
    }

    public byte[] getValue() {
        return value;
    }

    public void setValue(byte[] value) {
        this.value = value;
    }

    public static Any pack(GeneratedMessage message) {
        Any any = new Any();
        any.typeUrl = message.getClass().getName();
        any.value = message.toByteArray();
        return any;
    }

    public <T extends GeneratedMessage> T unpack(Class<T> clazz) {
        if (!typeUrl.equals(clazz.getName())) {
            throw new RuntimeException("Type mismatch");
        }
        try {
            return clazz.getMethod("parseFrom", byte[].class)
                       .invoke(null, value);
        } catch (Exception e) {
            throw new RuntimeException("Failed to unpack", e);
        }
    }

    public byte[] toByteArray() {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        try {
            if (typeUrl != null) {
                writeString(stream, 1, typeUrl);
            }
            if (value != null) {
                writeBytes(stream, 2, value);
            }
            return stream.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException("Failed to serialize", e);
        }
    }

    public static Any parseFrom(byte[] data) {
        Any result = new Any();
        ByteArrayInputStream stream = new ByteArrayInputStream(data);
        try {
            while (stream.available() > 0) {
                int tag = readTag(stream);
                switch (getFieldNumberFromTag(tag)) {
                    case 1:
                        result.typeUrl = readString(stream);
                        break;
                    case 2:
                        result.value = readBytes(stream);
                        break;
                    default:
                        skipField(stream, tag);
                        break;
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Failed to parse", e);
        }
        return result;
    }
}
相关推荐
菜鸟起航ing4 分钟前
【Java面试系列】Spring Cloud微服务架构中的分布式事务实现与性能优化详解 - 3-5年Java开发必备知识
java·spring cloud·微服务·面试·分布式事务
Java手札8 分钟前
为什么选择Redis?解析核心使用场景与性能优化技巧
java·spring boot·redis·intellij-idea
龙大大L1 小时前
第五章:5.1 ESP32物联网应用 - MQTT协议深度教程
java·单片机·struts·apache
极客先躯1 小时前
高级java每日一道面试题-2025年4月01日-微服务篇[Nacos篇]-Nacos集群的数据一致性是如何保证的?
java·开发语言·微服务
麓殇⊙2 小时前
springboot--页面的国际化
java·spring boot·后端
橙序研工坊2 小时前
JavaWeb-01-前端Web开发(HTML+CSS)
java·前端·css·html·javaweb
码农幻想梦2 小时前
4185 费马小定理求逆元
java·开发语言
汤姆大聪明2 小时前
微服务与Spring Cloud Alibaba简介
java·spring boot·spring·spring cloud·微服务
虾球xz2 小时前
游戏引擎学习第197天
java·学习·游戏引擎
唐人街都是苦瓜脸2 小时前
Java中常见的设计模式
java·开发语言·设计模式