如何在Databricks的SparkListener中使用dbutils并解决空指针异常
问题原因
你遇到的空指针异常核心来自三个问题:
- 依赖配置错误:
dbutils-api是仅用于编译期的接口包,你用implementation引入会将其打包到最终Jar中,和Databricks集群自带的dbutils实现类冲突。 - 初始化时机不匹配:
DBUtilsHolder中的dbutils实例是Databricks运行时在SparkContext完全启动后才注入的,而通过spark.extraListeners注册的SparkListener会在SparkContext启动的早期阶段完成实例化,你提前静态导入dbutils会在Listener类加载/实例化阶段就触发对未初始化实例的访问,直接引发空指针。 - 版本冲突问题:你注释说明项目用Scala 2.13,但所有依赖(包括Spark、dbutils-api)都是Scala 2.12版本,且Azure Databricks Runtime 9.1内置Scala版本为2.12,版本不匹配也会引发运行时错误。
修复步骤
1. 修正build.gradle依赖配置
将dbutils-api和Spark相关依赖都改为编译期依赖,不要打包到自有Jar中,避免和集群内置依赖冲突,同时对齐依赖版本和Databricks Runtime 9.1的内置组件版本:
dependencies { // 统一使用Scala 2.12,和Databricks Runtime 9.1匹配 implementation 'org.scala-lang:scala-library:2.12.15' // dbutils-api仅编译期需要,运行时用集群自带实现 compileOnly group: 'com.databricks', name: 'dbutils-api_2.12', version: '0.0.5' implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.32' // Spark相关依赖也仅编译期需要,版本对齐Runtime9.1内置的Spark 3.1.2 compileOnly group: 'org.apache.spark', name: 'spark-core_2.12', version: '3.1.2' compileOnly group: 'org.apache.spark', name: 'spark-sql_2.12', version: '3.1.2' implementation 'com.google.guava:guava:30.1.1-jre' // 其余测试相关依赖保持不变 }
2. 调整Listener代码中dbutils的获取逻辑
去掉dbutils的静态导入,改为在onApplicationStart方法中、确认运行在Databricks环境后再动态获取实例,避免类加载阶段就触发对未初始化实例的访问:
package my.custom.listener import org.apache.spark.scheduler.{SparkListener, SparkListenerApplicationStart} import org.slf4j.LoggerFactory class LibraryListener extends SparkListener { private var isDatabricks = false val log = LoggerFactory.getLogger(classOf[LibraryListener]) override def onApplicationStart(applicationStart: SparkListenerApplicationStart): Unit = { log.info("HELLO WORLD!") log.info(s"App Name ${applicationStart.appName}") log.info(s"User ${applicationStart.sparkUser}") isDatabricks = sys.env.get("DATABRICKS_RUNTIME_VERSION").nonEmpty if (isDatabricks){ log.info("WE ARE USING DATABRICKS!") // 运行到此处时SparkContext已完成初始化,dbutils实例已注入,动态获取即可 val dbutils = com.databricks.dbutils_v1.DBUtilsHolder.dbutils // dbutils.fs.ls返回值是数组,需转成字符串再打印 log.info(dbutils.fs.ls("dbfs:/").mkString("[", ", ", "]")) } } }
如果想要更稳妥、避免依赖dbutils-api的类加载问题,可以用反射方式获取dbutils实例,不需要引入dbutils-api依赖也能编译通过。
内容的提问来源于stack exchange,提问作者Will J
相关产品推荐
相关产品推荐

