Kotlin Academy · 课时

@DslMarker:防止接收者泄漏

使用 @DslMarker 注解在编译时强制执行 DSL 嵌套规则。

第 2 / 4 课13 个步骤

@DslMarker:防止接收者泄漏 是 CoddyKit 上的免费 Kotlin Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Kotlin Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Kotlin Academy 课程共包含 4 节课。

接收者泄漏问题

在嵌套 DSL 中,内部构建器 Lambda 表达式可能会意外访问外部接收者的成员。例如,在 td { } 代码块内部,您可能会调用来自外部表格作用域的 tr { },而这几乎从来都不是预期行为。

泄漏的表现

如果没有 @DslMarker,编译器会允许从任何内部 Lambda 表达式调用外部作用域的函数,从而导致令人困惑且容易出错的 DSL 用法:

// Without @DslMarker — this compiles but is wrong:
html {
    body {
        table {
            tr {                // outer scope leaks in
                tr {  }         // calling tr inside tr — nonsensical
            }
        }
    }
}

引入 @DslMarker

@DslMarker 是一种应用于您自己的注解类的元注解。任何使用您的 DSL 注解进行标注的构建器类,其隐式接收者都会在使用相同 DSL 注解的嵌套 Lambda 表达式中隐藏。

@DslMarker
annotation class HtmlDsl

@HtmlDsl
class Table

@HtmlDsl
class Tr

隐式接收者隐藏的工作方式

当两个嵌套 Lambda 表达式都拥有使用同一个 @DslMarker 注解标记的接收者时,外部接收者会在内部 Lambda 表达式中隐藏。直接访问它会导致编译错误。

@DslMarker
annotation class HtmlDsl

@HtmlDsl class Html
@HtmlDsl class Body
@HtmlDsl class Div

使用 @DslMarker 的完整示例

声明注解、标记构建器类,然后由编译器在编译时捕获对外部作用域方法的嵌套调用:

@DslMarker
annotation class MyCfgDsl

@MyCfgDsl
class ServerBuilder {
    var port: Int = 8080
    fun database(block: DbBuilder.() -> Unit) { /*...*/ }
}

@MyCfgDsl
class DbBuilder {
    var url: String = ""
    // Cannot call ServerBuilder.port here — compiler error
}

使用 this@ 绕过限制

如果确实需要从内部 Lambda 表达式访问外部接收者,请使用带标签的 this@OuterClass 引用。与意外的接收者泄漏不同,这种方式明确表达了访问意图。

fun ServerBuilder.database(block: DbBuilder.() -> Unit) {
    val db = DbBuilder()
    db.block()
    val serverPort = this@ServerBuilder.port  // explicit outer access
}

@DslMarker 与多个 DSL

不同的 DSL 应使用不同的 @DslMarker 注解。即使嵌套使用,使用不同标记且彼此无关的两个 DSL 也不会相互干扰。

@DslMarker annotation class HtmlDsl
@DslMarker annotation class GradleDsl
// HtmlDsl and GradleDsl receivers do not hide each other

应用于构建器和接收者

将 DSL 注解应用于构建器类本身。该注解会传播到所有接收者类型为该类的 Lambda 参数上。无需再为单个函数添加注解。

IDE 支持

使用 @DslMarker 时,IntelliJ IDEA 和 Android Studio 会立即将对外部接收者的访问标记为错误。在编写或使用 DSL 时,这可以提供实时反馈。

面向安全性进行设计

优秀的 DSL 设计会将每个构建器类与 @DslMarker 配合使用。虽然只增加了少量注解开销,却能大幅改善 DSL 的易用性,并防止 DSL 用户遇到整类错误。

标准库中的使用

Kotlin 自己的 @DslMarker 被用于 kotlinx.html、Ktor 路由 DSL、Gradle Kotlin 脚本以及 buildMap/buildList 构建器中。它是任何成熟 DSL 工具包中的基础工具。

快速检查

@DslMarker 可以防止什么?

回顾:@DslMarker

关键要点:

  • 没有 @DslMarker 时,外部接收者会泄漏到内部 DSL Lambda 表达式中
  • 定义一个带元注解的注解类,并将其应用于 DSL 中的所有构建器类
  • 使用相同标记的嵌套 Lambda 表达式无法意外调用外部作用域成员
  • 确实需要访问外部作用域时,请使用 this@OuterClass
免费开始

用 AI 导师学习 Kotlin — 免费

在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。

课程
51
课程
203

常见问题解答

「@DslMarker:防止接收者泄漏」课时是免费的吗?

是的 — 「@DslMarker:防止接收者泄漏」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Kotlin Academy 课程的其余内容,请升级到 CoddyKit PRO。 Kotlin Academy 课程共包含 4 节课。

「@DslMarker:防止接收者泄漏」这节课中我会学到什么?

使用 @DslMarker 注解在编译时强制执行 DSL 嵌套规则。 你通过在浏览器中直接运行的动手代码来练习 Kotlin Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Kotlin Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Kotlin Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「@DslMarker:防止接收者泄漏」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Kotlin Academy 课中编写并运行代码吗?

能。每节 Kotlin Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 带接收者的 Lambda:DSL 基础
  2. @DslMarker:防止接收者泄漏
  3. 构建类型安全的 HTML/Config DSL
  4. 在不破坏用户代码的情况下测试和演进 DSL
← 返回 Kotlin Academy