You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apache Avro编译失败:生成代码变量缺失美元符号$

Apache Avro Gradle插件编译异常问题排查

问题现象

搭建用于Kafka消息序列化与反序列化的Apache Avro模块时,测试多款Gradle Avro插件均触发不同报错:

  • 引入com.bakdata.avro 1.0.1版本插件时,报错无法找到资源/org/apache/avro/compiler/specific/templates/java/classic/enum.vm
  • 引入com.github.davidmc24.gradle.plugin.avro-base 1.3.0版本插件时,报错找不到generateAvroProtocol()方法
  • 引入com.commercehub.gradle.plugin.avro 0.99.99版本插件时,报错outputDir属性缺失输入或输出注解
  • 引入org.betterplugin.avro 0.19.2-SNAPSHOT版本插件时,插件完成度最高,可正常生成Java文件与协议文件,但生成的部分Java文件存在变量名缺失美元符号的问题,直接导致编译失败。

对应的build.gradle配置如下:

plugins {
    id "org.betterplugin.avro" version "0.19.2-SNAPSHOT"

    // Error: Unable to find resource '/org/apache/avro/compiler/specific/templates/java/classic/enum.vm'
    // id "com.bakdata.avro" version "1.0.1"

    // Error: Could not find method generateAvroProtocol()
    // id "com.github.davidmc24.gradle.plugin.avro-base" version "1.3.0"

    // Error: property 'outputDir' is missing an input or output annotation.
    // id "com.commercehub.gradle.plugin.avro" version "0.99.99"
}

group = 'com.example'
description = 'AVRO Library'

dependencies {
    implementation "org.apache.avro:avro:1.11.0"
}

generateAvroProtocol {
    source("src/main/resources/avro")
    outputDir = file("build/generated-main-avro-protocol")
}

generateAvroJava {
    source("src/main/resources/avro")
    outputDir = file("build/generated-main-avro-java")
}

org.betterplugin.avro插件生成的Java代码put方法存在明确语法错误:方法参数明确定义为value$,但空判断逻辑中引用的是不带$的value,触发cannot find symbol variable value编译错误,问题代码片段如下:

// Used by DatumReader.  Applications should not call.
  @SuppressWarnings(value="unchecked")
  public void put(int field$, java.lang.Object value$) {
    switch (field$) {
    case 0: EXAMPLE_A = value != null ? value$.toString() : null; break;
    case 1: EXAMPLE_B = value != null ? value$.toString() : null; break;
    case 2: EXAMPLE_C = value != null ? value$.toString() : null; break;
    default: throw new org.apache.avro.AvroRuntimeException("Bad index");
    }
  }

核查avro-compiler用于生成代码的record.vm模板,模板对应位置明确编写了带$的value$引用,模板源码如下:

// Used by DatumReader.  Applications should not call.
  @SuppressWarnings(value="unchecked")
  public void put(int field$, java.lang.Object value$) {
    switch (field$) {
#set ($i = 0)
#foreach ($field in $schema.getFields())
    case $i: ${this.mangle($field.name(), $schema.isError())} = #if(${this.javaType($field.schema())} != "java.lang.Object" && ${this.javaType($field.schema())} != "java.lang.String")(${this.javaType($field.schema())})#{end}value$#if(${this.javaType($field.schema())} == "java.lang.String") != null ? value$.toString() : null#{end}; break;
#set ($i = $i + 1)
#end
    default: throw new IndexOutOfBoundsException("Invalid index: " + field$);
    }
  }

根因分析

其他三款插件报错原因

  • com.bakdata.avro 1.0.1:插件内置依赖的Avro编译器版本与项目引入的Avro 1.11.0版本不兼容,旧版Avro编译器的模板资源路径与1.11.x版本不一致,导致找不到enum.vm模板资源。
  • com.github.davidmc24.gradle.plugin.avro-base 1.3.0:avro-base是该插件的核心基础包,仅提供任务类型定义,不会自动注册generateAvroProtocol、generateAvroJava等具体任务,这些任务由同系列的完整版插件提供,仅引入base包自然找不到对应方法。
  • com.commercehub.gradle.plugin.avro 0.99.99:插件版本过旧,未适配Gradle 7.0+的任务属性校验规则——Gradle 7之后强制要求所有任务的输入、输出属性必须标注@Input、@OutputDirectory等对应注解,未标注的属性会直接触发构建报错。

SNAPSHOT版插件生成代码丢失$符号的原因

该问题由Apache Velocity模板引擎的默认解析规则导致:

  1. Velocity中$是变量引用的起始标记,只有$后紧跟合法变量名(字母、数字、下划线)或属性/方法调用符(.)时,才会被识别为变量语法;如果$后紧跟空格、!等非变量语法字符,Velocity默认会将这个孤立的$静默删除,不输出到最终结果。
  2. 模板中value$ != null的片段里,$后面紧跟空格,因此被Velocity判定为无效标记直接丢弃,最终输出为value != null;而后续value$.toString()片段里,$后面紧跟.,被Velocity识别为合法的变量属性调用语法,因此$被保留,就出现了同一段代码里部分value$丢了$、部分保留的现象。
  3. 该SNAPSHOT版本插件未对Velocity做字面量转义兼容配置,也没有修正模板中的转义写法,是插件本身的未修复bug。

修复方案

优先选择稳定维护的正式版插件,不要使用未正式发布的SNAPSHOT版本插件:

  1. 替换插件为目前社区维护最活跃、版本适配最完善的com.github.davidmc24.gradle.plugin.avro正式版,注意不要仅引入-base包,直接引入完整版即可,选择1.5.0+版本可完美兼容Gradle 7+和Avro 1.11.x版本,配置示例:
plugins {
    id "com.github.davidmc24.gradle.plugin.avro" version "1.6.0"
}

group = 'com.example'
description = 'AVRO Library'

dependencies {
    implementation "org.apache.avro:avro:1.11.0"
}

// 插件默认会自动扫描src/main/avro路径下的avsc/avdl/avpr文件,自动注册代码生成任务,不需要手动声明generateAvroJava/generateAvroProtocol
// 自定义配置示例
avro {
    outputDir = file("build/generated-main-avro-java")
    stringType = "String" // 生成String类型而不是Utf8类型,适配Kafka序列化场景
}
  1. 如果因特殊原因必须使用org.betterplugin.avro的SNAPSHOT版本,需要自定义修改Avro代码生成模板,将模板中所有作为字面量输出的value$、field$中的$做Velocity转义,替换为${'$'},比如把value$ != null改为value${'$'} != null,强制Velocity输出$符号即可。

内容的提问来源于stack exchange,提问作者bdetweiler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 07:01:59