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模板引擎的默认解析规则导致:
- Velocity中
$是变量引用的起始标记,只有$后紧跟合法变量名(字母、数字、下划线)或属性/方法调用符(.)时,才会被识别为变量语法;如果$后紧跟空格、!等非变量语法字符,Velocity默认会将这个孤立的$静默删除,不输出到最终结果。 - 模板中
value$ != null的片段里,$后面紧跟空格,因此被Velocity判定为无效标记直接丢弃,最终输出为value != null;而后续value$.toString()片段里,$后面紧跟.,被Velocity识别为合法的变量属性调用语法,因此$被保留,就出现了同一段代码里部分value$丢了$、部分保留的现象。 - 该SNAPSHOT版本插件未对Velocity做字面量转义兼容配置,也没有修正模板中的转义写法,是插件本身的未修复bug。
修复方案
优先选择稳定维护的正式版插件,不要使用未正式发布的SNAPSHOT版本插件:
- 替换插件为目前社区维护最活跃、版本适配最完善的
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序列化场景 }
- 如果因特殊原因必须使用
org.betterplugin.avro的SNAPSHOT版本,需要自定义修改Avro代码生成模板,将模板中所有作为字面量输出的value$、field$中的$做Velocity转义,替换为${'$'},比如把value$ != null改为value${'$'} != null,强制Velocity输出$符号即可。
内容的提问来源于stack exchange,提问作者bdetweiler
相关产品推荐
相关产品推荐

