猜你喜欢
深入实践Kotlin元编程

深入实践Kotlin元编程

书籍作者:霍丙乾 ISBN:9787111732549
书籍语言:简体中文 连载状态:全集
电子书格式:pdf,txt,epub,mobi,azw3 下载次数:6367
创建日期:2024-04-29 发布日期:2024-04-29
运行环境:PC/Windows/Linux/Mac/IOS/iPhone/iPad/Kindle/Android/安卓/平板
内容简介
这是一本从基础知识、设计思想、技术方案、应用方法、实践技巧5个维度系统讲解Kotlin元编程,并以此大幅提升Kotlin工程师开发水平、研发效率和开发体验的著作。
作者是Kotlin领域的资深专家和布道者,本书源于他对Kotlin编译器源码的反复研读和大量的工程实践,不仅细致讲解了反射、程序静态分析、 Java注解处理器、Kotlin符号处理器、Kotlin编译器插件、元程序的开发和调试等核心元编程技术,而且详细剖析了Jetpack Compose的编译器插件和IntelliJ 插件、AtomicFU 的 JVM 字节码和JavaScript代码的生成逻辑。本书的出版打破了元编程技术资料少、门槛高的行业现状。
本书包含大量案例,这些案例大多来自真实的生产实践,相对成熟和完善,可以作为元编程项目的范本。同时,本书提供大量的代码,为了提升阅读体验,在注释、书写和排版等方面对代码做了精心的优化。全书的源文件均可免费下载,读者可以通过作者的网站实时与作者互动和交流。
作者简介

霍丙乾

资深移动客户端技术专家,现为猿辅导资深 Android 技术专家,曾就职于腾讯。Google 开发 者专家(Kotlin 方向),国内知名的 Kotlin 布道师, 尤其擅长使用元编程技术提升工程效率、改善开发 体验等。在 Java、Kotlin、Android 等技术方向 有深厚积累,对移动客户端跨平台等技术有深入的 理解。 热衷于开源软件的应用和推广,以及 IT 技术 的传播和分享。著有畅销书《深入理解 Kotlin 协 程》;慕课网精英讲师,在慕课网上线多门课程; 在 B 站、微信公众号等多个平台持续发布教学视 频和技术文章,广受欢迎。 不遗余力地推广 Kotlin,曾受邀在全球移动 开发者峰会、Kotlin 中文开发者大会、Android 技 术大会、JetBrains 开发者大会等 10 余个大会上 分享关于 Kotlin 的实践和思考。


编辑推荐
适读人群 :本书适合所有的有 Java、Kotlin 基础并了解注解、反射、注解处理器等相关知识的读者阅读。
(1)作者背景资深:作者先后就职于腾讯和猿辅导,是中国Kotlin社区知名布道者和技术专家,Google开发者专家(Kotlin方向)
(2)作者经验丰富:作者在Kotlin领域有大量的项目实践经验,对Kotlin编译器源码有深入研究,著有畅销书《深入理解 Kotlin 协程》。
(3)内容系统深入:作者结合Kotlin编译器源码和工程实践经验,从基础知识、设计思想、技术方案、应用方法、实践技巧5个维度系统讲解Kotlin元编程。
(4)理论实战兼备:不仅详细讲解了元编程的常见核心技术,而且提供了大量来自真实生产环境的案例及代码,图文并茂。
前言

Preface 前 言

为何写作本书

2018年,我受邀在“JetBrains开发者日—2018中国巡演”活动中做题为“如何优雅地使用Kotlin数据类”的分享。在准备这次分享时,我花了几天时间调研为Kotlin的数据类提供深复制能力的可行性,并给出了基于Kotlin反射和Java注解处理器(APT)的实现方案,也就是后来开源的DeepCopy项目。当时我尝试过编写一款编译器插件来实现这个需求,不过最终因为对Kotlin编译器的了解有限而未能如愿。

2021年,我受邀在Google开发者社区主办的“社区说”活动中做题为“Kotlin编译器插件:我们究竟在期待什么?”的分享。这一次,我花了两周时间初步基于Kotlin符号处理器(KSP)和编译器插件实现了数据类的深复制,整个过程充满了探索的乐趣。

为了加深对Kotlin编译器的认识,我基于Kotlin编译器插件完成了可以实现类似于Android的@IntDef功能的ValueDef编译器插件。事实上,ValueDef的功能更强大,

@IntDef只会在代码编写时提供错误提示,而ValueDef除了会提供错误提示以外,还会在编译时报错。

与此同时,随着Kotlin符号处理器的开源和Jetpack Compose的发布,大家对Kotlin元编程的关注度也在逐步提升,但这方面的相关资料非常少。

于是,我向机械工业出版社的杨福川老师提出了把这些内容整理成书的想法,得到了他的肯定和支持。有了编写《深入理解Kotlin协程》的经验,我很快就正式开始了这本书的写作。

本书主要特点

“元编程”是一个比较庞大的话题,本书主要介绍了生产实践中应用较为广泛的反射、Java注解处理器、Kotlin符号处理器、Kotlin编译器插件、Kotlin语法分析等元编程相关的内容。

与一般的语法知识不同,元编程相关的内容通常较为抽象。为了更好地让读者理解元编程相关的各项技术,本书提供了丰富的应用案例。这些案例相对成熟和完善,可以作为元编程项目的范本。

本书基本上遵循了基础知识介绍和案例实践的结构。以第3章为例,3.1节和3.2节系统地介绍了Java反射和Kotlin反射的概念和使用方法,是基础知识介绍部分;3.3~3.5节通过案例进一步介绍反射的适用场景,是案例实践部分。

本书的实践案例通常包括案例背景、需求分析和案例实现这几方面。

案例背景:介绍案例的需求背景。本书的案例大多源自真实的生产实践,因此案例背景的介绍有非常重要的价值。

需求分析:明确需求的细节,拆解需求并转换成技术方案。

案例实现:提供详细的问题解决思路以及案例实现步骤。

在系统介绍了常见的Kotlin元编程技术之后,本书还对Jetpack Compose的编译器插件和IntelliJ插件、AtomicFU的字节码JavaScript代码逻辑做了详细的剖析。

与绝大多数技术书类似,本书包含了大量代码。为了提升阅读体验,我在编写本书时对代码做了以下优化:

省略不必要的部分,避免代码冗长而浪费篇幅。

代码缩进为2个空格,以降低缩进对阅读体验的影响。

核心代码注释覆盖率不低于30%,方便读者快速理解代码的含义。

核心代码单行长度不超过80个字符,避免排版后出现折行的问题。

在部分代码清单的开始处标注其所在的模块、文件或者函数等信息,方便读者自行查找相关源代码。

代码字体采用JetBrains Mono,该字体由Kotlin项目团队所属公司JetBrains为开发者专门打造,更适合代码的阅读。

本书阅读对象

本书探讨的内容有一定的复杂度。在阅读本书之前,读者需要对Kotlin语言的语法有较为深入的理解,也需要具备一定的编译原理的基础知识。

本书适用于有一定基础的Kotlin开发者,包括但不限于正在使用和希望使用Kotlin开发Android、Web服务、iOS、前端等应用的开发者。

本书非常适用于希望在Kotlin相关开发领域实现进阶的读者。本书介绍的内容对读者提升自身编程水平以及团队提升研发效率都有非常大的参考价值。

本书不会介绍Kotlin的基础语法,因此建议Kotlin初学者先阅读相关基础书。

如何阅读本书

本书基于Kotlin 1.8.0系统地介绍了Kotlin元编程的基本概念、技术方案、应用场景和实践技巧。

本书主要分为三部分,分别介绍如下。

第一部分为元编程的基础知识(第1章和第2章),为后续的元编程实践提供知识储备。如果读者有一定的Kotlin元编程基础,可以直接阅读第二部分内容。在阅读过程中,如果遇到概念相关的问题,也可以随时翻阅这部分内容。

第二部分为元编程的技术实践(第3~8章),涉及运行时的反射、源代码生成、编译时的符号处理、程序静态分析、编译器插件、元程序的开发和调试等内容。这部分的章节安排相对独立,读者可以根据自己的实际需求选择阅读相应的章节。需要说明的是,DeepCopy项目是贯穿这部分内容的综合案例,在介绍每一种元编程技术方案时,我们都会给出DeepCopy项目中对应的技术方案的实现,希望能够帮助读者加深对不同的元编程技术方案的认识。在了解了元编程的常见技术之后,本书在第8章重点介绍了元编程项目实践中编写单元测试和集成测试的常见方法与技巧,以提升读者开发元编程项目的

效率。

第三部分为综合案例(第9章和第10章)。第9章对Jetpack Compose的编译器插件和IntelliJ插件做了详细的剖析,这一章内容实际上是对第7章的延伸。通过阅读这一章,读者可以进一步加深对Jetpack Compose的设计思路的认识,同时也能充分领略编译器插件的魅力。第10章对AtomicFU的编译产物处理进行了详细的剖析,包括对Kotlin JVM的编译产物JVM字节码和Kotlin JS的编译产物JavaScript代码的处理,这一章内容是对Kotlin元编程的拓展和延伸。

本书的约定

本书正文中涉及的外部依赖版本均使用$version来占位,以便我在本书的配套源代码中统一维护。

本书正文中出现的类、函数或者属性,通常采用“类名#函数名”或者“类名#属性

名”的书写格式,这种格式可以有效地区分出类名的包名部分,也可以使读者直接在IntelliJ IDEA中通过该格式快速定位到对应的函数或者属性。例如Resolver#getClassDeclaration-ByName(String)是指Resolver类的getClassDeclarationByName函数,参数类型为String。如果函数的参数列表不存在歧义,我也会省略其参数列表,例如DeclarationChecker# check(...)。

本书的代码清单以Kotlin代码为主,涉及其他编程语言的代码清单会在第一行给出标识。例如:

代码清单10-57 移除对value属性的访问之后的结果

[JavaScript]

var value = state;

本书为部分代码清单提供了位置说明,以便读者查找,例如:

代码清单3-14 公共标准库中KClass的定义

//模块:kotlin-stdlib-common;文件:KClass.kt

public expect interface KClass : KClassifier {

public val simpleName: String?

public val qualifiedName: String?

public fun isInstance(value: Any?): Boolean

}

这段代码给出了公共标准库中KClass的定义,它定义在kotlin-stdlib-common模块的KClass.kt文件中。基于这些位置信息,读者可以方便地在Kotlin官方源代码中找到对应的代码来了解更多信息。

为了避免影响阅读体验,本书还对不必要的代码做了省略,例如:

代码清单5-17 获取符号的修饰符

val element: Element = ...

// getModifiers在Kotlin中被当作只读属性modifiers

val modifiers = element.modifiers

这段代码的目的是介绍如何获取Element的修饰符,因此省略了Element实例的获取过程。

本书在展示编译器错误提示信息时,统一采用了以下格式:

目标代码

^^

-------

错误信息

-------

下面是一个具体的例子。

代码清单9-33 f1在内联过程中与DisallowComposableCalls产生冲突

@Composable inline

fun F1(f1: () -> Unit) {

^^^^^^^^^^^^^^

---------------------------------------------------------------------

[MISSING_DISALLOW_COMPOSABLE_CALLS_ANNOTATION]

Parameter f1 cannot be inlined inside of lambda argument block of F2

without also being annotated with @DisallowComposableCalls

---------------------------------------------------------------------

F2 { f1() }

}

编译器给这段代码中的函数F1的参数部分提供了内容为“Parameter f1 cannot be inlined inside of lambda argument block of F2 without also being annotated with @DisallowComposableCalls”的错误信息,这段错误信息在Kotlin编译器(或者编译器插件)中对应名为“MISSING_DISALLOW_COMPOSABLE_CALLS_ANNOTATION”的错误。

勘误和支持

由于水平有限,编写时间仓促,以及技术不断更新,书中难免会出现一些错误或者不准确的地方,恳请读者批评指正。

读者可以通过以下方式提供反馈:

1)关注微信公众号“霍丙乾bennyhuo”,回复“Kotlin元编程”,在收到的消息页面评论留言。

2)在本书主页https://www.bennyhuo.com/project/kotlin-metaprogramming.html评论留言。

本书主页上提供勘误表,我会在收到反馈后及时将问题整理并补充到勘误表中,对于一些比较重要的问题也会专门通过微信公众号和我的个人网站提供补充材料。

书中的全部源文件可以从https://github.com/bennyhuo/KotlinMetaProgrammingInAction-Sources下载。如果你有更多的宝贵意见,也欢迎发送邮件至[email protected],期待得到你们的真挚反馈。

致谢

感谢我的家人,是他们一如既往地支持才让我在成长过程中敢于尝试和坚持,也是他们的陪伴才让我有时间和精力去完成这样一本书。

感谢小猿口算团队的同事们。得益于猿辅导公司内部良好的技术氛围,我和团队的同事们有机会在提升研发效率、优化程序架构上深入探索Kotlin的各种元编程能力,这为本书的写作提供了素材。

感谢Kotlin中文社区和Google开发者社区的朋友们,本书的许多内容都曾在社区组织的一次次活动中得以锤炼和提升。

感谢参与审稿的朋友们,他们是李涛、孙国栋、陈轲、孟祥钊、2BAB、程序员江同学、鹿瑞朋、贾彦伟、乔禹昂和叶楠。特别感谢李涛,他也是为《深入理解Kotlin协程》贡献了最多勘误的读者。

谨以此书献给所有Kotlin开发者!


霍丙乾

2023年4月2日


目录

目 录 Contents

前言

第一部分 元编程的基础知识

第1章 元编程概述2

1.1 元编程的需求背景2

1.2 元编程的基本概念4

1.2.1 元编程的定义5

1.2.2 元编程的分类5

1.3 元编程的学习方法6

1.3.1 培养兴趣6

1.3.2 付诸行动6

1.3.3 善用工具7

1.3.4 多读源代码8

1.4 常用项目的调试环境配置8

1.4.1 Java编译器8

1.4.2 Kotlin编译器11

1.4.3 IntelliJ社区版13

1.4.4 Jetpack Compose编译器插件19

1.5 本章小结21

第2章 元数据概述22

2.1 基本概念22

2.1.1 语法结构23

2.1.2 编译产物23

2.2 注释23

2.2.1 注释的结构化23

2.2.2 文档生成24

2.3 注解25

2.3.1 注解的概念25

2.3.2 源代码可见的注解26

2.3.3 二进制可见的注解27

2.3.4 运行时可见的注解30

2.4 Kotlin的元数据31

2.4.1 Kotlin JVM中的@Metadata

   注解31

2.4.2 Kotlin JVM模块中的元数据35

2.4.3 klib中的元数据37

2.5 Kotlin的语法树39

2.5.1 Kotlin的语法定义40

2.5.2 基于IntelliJ平台接口的抽象语

   法树41

2.5.3 新一代语法树FIR42

2.5.4 连接前后端编译器的IR43

2.5.5 Java和Kotlin的符号树45

2.6 Kotlin的编译产物47

2.6.1 JVM47

2.6.2 JavaScript48

2.6.3 Native48

2.7 本章小结49

第二部分 元编程的技术实践

第3章 运行时的反射52

3.1 Java反射52

3.1.1 基本功能52

3.1.2 解除访问限制53

3.1.3 动态代理54

3.1.4 对注解的支持55

3.1.5 对方法参数名的支持56

3.1.6 访问Kotlin代码57

3.2 Kotlin反射58

3.2.1 基本功能59

3.2.2 类引用的获取61

3.2.3 属性引用和函数引用65

3.2.4 typeOf67

3.2.5 dynamic类型69

3.2.6 属性委托70

3.3 案例:Retrofit的接口实现72

3.3.1 Retrofit基本用法72

3.3.2 GitHubService实例的创建73

3.3.3 函数参数与请求参数的

对应关系74

3.3.4 泛型类型的反序列化74

3.3.5 案例小结75

3.4 案例:使用反射实现DeepCopy75

3.4.1 案例背景75

3.4.2 需求分析76

3.4.3 案例实现78

3.4.4 小试牛刀79

3.4.5 案例小结79

3.5 案例:使用dynamic类型为

   Kotlin JS实现DeepCopy80

3.5.1 案例背景80

3.5.2 需求分析80

3.5.3 案例实现83

3.5.4 案例小结83

3.6 本章小结84

第4章 源代码生成85

4.1 直接输出目标代码85

4.1.1 一个简单的例子85

4.1.2 标准库的代码生成87

4.2 案例:为Kotlin添加Tuple类型88

4.2.1 案例背景88

4.2.2 需求分析90

4.2.3 案例实现91

4.3 使用模板引擎生成目标代码93

4.3.1 Anko中的代码生成93

4.3.2 使用模板引擎渲染目标代码95

4.4 案例:为Java静态方法生成

  Kotlin扩展函数(模板引擎)96

4.4.1 案例背景96

4.4.2 需求分析96

4.4.3 案例实现98

4.4.4 代码优化101

4.5 使用代码生成框架生成目标代码104

4.5.1 JavaPoet104

4.5.2 KotlinPoet109

4.6 案例:为Java静态方法生成

  Kotlin扩展函数(KotlinPoet)114

4.6.1 类型的映射114

4.6.2 实现代码生成116

4.6.3 泛型参数的支持118

4.7 本章小结121

第5章 编译时的符号处理122

5.1 符号的基本概念122

5.1.1 Java的符号122

5.1.2 Kotlin的符号124

5.1.3 符号与语法树节点的关系和

   区别125

5.2 处理器的基本结构125

5.2.1 APT的基本结构125

5.2.2 KSP的基本结构130

5.2.3 APT与KSP的结构差异131

5.2.4 处理器的配置文件132

5.3 深入理解符号和类型132

5.3.1 获取修饰符133

5.3.2 通过名称获取符号133

5.3.3 获取符号的类型134

5.3.4 通过类型获取符号138

5.3.5 判断类型之间的关系139

5.3.6 获取注解及其参数值141

5.4 案例:基于源代码生成模块的

  符号文件144

5.4.1 案例背景144

5.4.2 案例实现:APT版本145

5.4.3 案例实现:KSP版本147

5.5 深入理解符号处理器148

5.5.1 如何使用APT处理Kotlin

   符号148

5.5.2 符号的有效性验证150

5.5.3 处理器的轮次和符号的延迟

   处理150

5.5.4 处理器对增量编译的支持151

5.5.5 多模块的符号处理154

5.6 案例:使用符号处理器实现

  DeepCopy156

5.6.1 案例背景156

5.6.2 需求分析156

5.6.3 案例实现:APT版本157

5.6.4 案例实现:KSP版本160

5.6.5 案例小结163

5.7 本章小结163

第6章 程序静态分析164

6.1 案例:检查项目中的数据类164

6.1.1 案例背景164

6.1.2 需求分析166

6.1.3 案例实现:使用正则表达式

   匹配167

6.1.4 案例小结169

6.2 Kotlin程序的语法分析169

6.2.1 需求扩展169

6.2.2 案例实现:使用Antlr实现

   语法树解析170

6.2.3 案例小结173

6.3 Kotlin程序的语义分析173

6.3.1 需求扩展173

6.3.2 案例实现:使用Kotlin编译器

   进行语义分析174

6.3.3 案例小结176

6.4 使用detekt进行静态扫描176

6.4.1 基于detekt实现数据类扫描177

6.4.2 使用detekt的IntelliJ插件178

6.5 基于IntelliJ IDEA进行语法检查180

6.5.1 IntelliJ IDEA中的代码检查180

6.5.2 实现对数据类的检查181

6.5.3 实现快捷修复操作182

6.6 本章小结184

第7章 编译器插件185

7.1 编译器插件概述185

7.1.1 什么是编译器插件185

7.1.2 编译器插件能做什么186

7.2 编译器插件项目的基本结构187

7.2.1 编译器插件模块187

7.2.2 编译工具链插件模块190

7.2.3 集成开发环境插件模块191

7.3 案例:trimIndent函数的编译时

  实现195

7.3.1 案例背景195

7.3.2 需求分析196

7.3.3 案例实现197

7.3.4 插件的发布202

7.3.5 案例小结205

7.4 案例:使用编译器插件实现

  DeepCopy205

7.4.1 案例背景205

7.4.2 需求分析205

7.4.3 案例实现206

7.4.4 案例小结212

7.5 符号处理器的实现原理212

7.5.1 Java存根的生成212

7.5.2 Java编译器的调用213

7.5.3 增量编译的支持214

7.5.4 多轮次符号处理215

7.5.5 注解实例的构造216

7.5.6 延伸:依赖关系分析217

7.6 本章小结217

第8章 元程序的开发和调试218

8.1 使用kotlin-compile-testing编写

   单元测试218

8.1.1 编译器的调用和调试218

8.1.2 检查KAPT的输出221

8.1.3 添加对KSP的支持222

8.1.4 运行编译后的程序223

8.1.5 打印变换之后的IR225

8.1.6 多模块编译227

8.2 使用kotlin-compile-testing-extensions

  简化单元测试228

8.2.1 测试数据的组织形式228

8.2.2 测试数据的加载230

8.2.3 编译运行并检查结果231

8.2.4 检查IR和运行时输出232

8.3 在实际项目中集成233

8.3.1 工程的组织形式234

8.3.2 单步调试Kotlin编译器235

8.3.3 Kotlin编译器的日志输出237

8.4 本章小结238

第三部分 综合案例

第9章 Jetpack Compose的编译

    时处理240

9.1 Jetpack Compose简介240

9.2 静态检查243

9.2.1 错误信息243

9.2.2 声明检查245

9.2.3 调用检查251

9.2.4 目标检查260

9.3 案例:为DeepCopy添加代码检查261

9.3.1 案例背景261

9.3.2 需求分析261

9.3.3 案例实现262

9.3.4 案例效果265

9.4 代码提示266

9.4.1 Composable函数的命名266

9.4.2 Composable函数调用的颜色270

9.5 Composable函数的变换272

9.5.1 $composer参数272

9.5.2 参数默认值277

9.5.3 参数的变化状态与重组的

   跳过机制284

9.6 本章小结299

第10章 AtomicFU的编译产物

    处理300

10.1 AtomicFU的由来300

10.2 Kotlin JVM平台的编译产物

   处理304

10.2.1 需求背景分析304

10.2.2 技术选型分析305

10.2.3 方案实现分析305

10.3 Kotlin JS平台的编译产物处理315

10.3.1 需求背景分析316

10.3.2 技术选型分析317

10.3.3 方案实现分析317

10.4 本章小结324


产品特色