1. 入口

本文的编译入口为 gradle 插件. 一般情况下, kotlin的gradle 工程一般都会引入这样一句话 apply plugin: 'kotlin'
开发过 gradle plugin 的同学都知道 pluginId 是定义在resourcexxx.properties里, 这里引用下Gradle官网的资料

  1. Example 41.7. Wiring for a custom plugin
  2. src/main/resources/META-INF/gradle-plugins/org.samples.greeting.properties
  3. implementation-class=org.gradle.GreetingPlugin

以此推断必定有一个叫 kotlin.properties的文件 , 然后很快找到了它 从编译层面来了解Kotlin(一) - 图1

  1. implementation-class=org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper

所以 kotlin 的插件入口类是 KotlinPluginWrapper
接着我们就会发现 javacompilekotlincompile 之间的关系

  1. internal fun configureJavaTask(kotlinTask: KotlinCompile, javaTask: AbstractCompile, logger: Logger) {
  2. skip...
  3. // Make Gradle check if the javaTask is up-to-date based on the Kotlin classes
  4. javaTask.inputs.dir(kotlinTask.destinationDir)
  5. // Also, use kapt1 annotations file for up-to-date check since annotation processing is done with javac
  6. kotlinTask.kaptOptions.annotationsFile?.let { javaTask.inputs.file(it) }
  7. javaTask.dependsOn(kotlinTask)
  8. skip...
  9. }

javaTask.dependsOn(kotlinTask), 这句话的意思就是在编译java 之前, 先执行kotlin 的编译
接下来我们继续跟踪KotlinCompile Task
Gradle Task的入口函数都会有个@TaskAction annotation , 根据这个特性, 我们很快就能找到编译的主入口runJvmCompiler
很快就可以追踪到真正编译 kotlin 的入口, CompileServiceImpl类的compile函数

  1. override fun compile(
  2. sessionId: Int,
  3. compilerArguments: Array<out String>,
  4. compilationOptions: CompilationOptions,
  5. servicesFacade: CompilerServicesFacadeBase,
  6. compilationResults: CompilationResults?
  7. ): CompileService.CallResult<Int> = ifAlive {
  8. ...skip...
  9. @Suppress("UNCHECKED_CAST")
  10. val compiler = when (targetPlatform) {
  11. CompileService.TargetPlatform.JVM -> K2JVMCompiler()
  12. CompileService.TargetPlatform.JS -> K2JSCompiler()
  13. CompileService.TargetPlatform.METADATA -> K2MetadataCompiler()
  14. } as CLICompiler<CommonCompilerArguments>
  15. ...skip...
  16. doCompile(sessionId, daemonReporter, tracer = null) { _, _ ->
  17. compiler.exec(messageCollector, Services.EMPTY, k2PlatformArgs)
  18. }
  19. }

接着我们就到 kotlin -> JVM 的入口了

  1. public static void doGenerateFiles(
  2. @NotNull Collection<KtFile> files,
  3. @NotNull GenerationState state,
  4. @NotNull CompilationErrorHandler errorHandler
  5. )

需要注意的是, 这个函数其中一个必要入参是 KtFile的集合. 有过intellij Idea Plugin开发经验的人应该知道这个是Inellij Idea 里对应语言文件的 psiFile , Java文件对应的也有PsiJavaFile
关于PSI File的具体了解,可以看这里www.jetbrains.org/intellij/sd…

2. 编译

由于 kotlin 自定义的一些语法糖比较丰富, 所以这里将编译部分分开来讲.

2.1 Property

kotlin 定义属性有以下几种

  1. class Address {
  2. val Country = "China"
  3. var street: kotlin.String = "name"
  4. lateinit var city: kotlin.String
  5. var state: kotlin.String? = null
  6. get() = ""
  7. var zip: Int? = null
  8. set(value) {
  9. field = value
  10. }
  11. val address: kotlin.String by kotlin.lazy { "$Country $city $street $state $zip" }
  12. var address1: kotlin.String by kotlin.properties.Delegates.observable(address) { _, old, new ->
  13. kotlin.io.println("oldValue = $old And newValue = $new")
  14. }
  15. }

在下面的内容之前, 有个概念大家需要知道.

Backing Fields Classes in Kotlin cannot have fields. However, sometimes it is necessary to have a backing field when using customaccessors. For these purposes, Kotlin provides an automatic backing field which can be accessed using the fieldidentifier:

大致意思是说, 在Kotlin 里没有fields的概念, 当用户需要自定义getter 或者 setter 时, 在自定义 accesser 里可以通过backing field来修改当前property的值, 例如上述var zip 如果没有field = value这句话, zip就永远无法被修改.
我们先看下Property的编译函数

  1. private void gen(
  2. @Nullable KtProperty declaration,
  3. @NotNull PropertyDescriptor descriptor,
  4. @Nullable KtPropertyAccessor getter,
  5. @Nullable KtPropertyAccessor setter
  6. ) {
  7. ...skip...
  8. genBackingFieldAndAnnotations(declaration, descriptor, false);
  9. if (isAccessorNeeded(declaration, descriptor, getter)) {
  10. generateGetter(declaration, descriptor, getter);
  11. }
  12. if (isAccessorNeeded(declaration, descriptor, setter)) {
  13. generateSetter(declaration, descriptor, setter);
  14. }
  15. }

一个Property 的转化成JVM可以分为三个步骤

  1. genBackingField
  2. genAnnotations
  3. genGetter && genSetter

那我们一个一个的看

2.1.1 genBackingField

入口函数

  1. private boolean generateBackingField(
  2. @NotNull KtNamedDeclaration p,
  3. @NotNull PropertyDescriptor descriptor,
  4. @NotNull Annotations backingFieldAnnotations,
  5. @NotNull Annotations delegateAnnotations
  6. ) {
  7. ...skip...
  8. if (p instanceof KtProperty && ((KtProperty) p).hasDelegate()) {
  9. generatePropertyDelegateAccess((KtProperty) p, descriptor, delegateAnnotations);
  10. }
  11. else if (Boolean.TRUE.equals(bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, descriptor))) {
  12. generateBackingFieldAccess(p, descriptor, backingFieldAnnotations);
  13. }
  14. ...skip...
  15. return true;
  16. }

从代码中可以看到, PropertyBackingField 的编译分为两个分支

  1. Delegate. 2. 非 Delegate 的变量

    Delegated Properties The most common kind of properties simply reads from (and maybe writes to) a backing field. On the other hand, withcustom getters and setters one can implement any behaviour of a property. Somewhere in between, there are certaincommon patterns of how a property may work. A few examples: lazy values, reading from a map by a given key, accessing adatabase, notifying listener on access, etc.

在上述的例子中, addressaddress1 是属于 Delegate.
DelegateProperty最终都以下面这种方式生成javabytecode

  1. FieldVisitor fv = builder.newField(
  2. JvmDeclarationOriginKt.OtherOrigin(element, propertyDescriptor), modifiers, name, type.getDescriptor(),
  3. isDelegate ? null : typeMapper.mapFieldSignature(kotlinType, propertyDescriptor), defaultValue
  4. );
  1. @Override
  2. @NotNull
  3. public FieldVisitor newField(
  4. @NotNull JvmDeclarationOrigin origin,
  5. int access,
  6. @NotNull String name,
  7. @NotNull String desc,
  8. @Nullable String signature,
  9. @Nullable Object value
  10. ) {
  11. FieldVisitor visitor = getVisitor().visitField(access, name, desc, signature, value);
  12. if (visitor == null) {
  13. return EMPTY_FIELD_VISITOR;
  14. }
  15. return visitor;
  16. }

到这里,kotlin 的编译方式大家应该就知道了 KtFile —> Asm —>ByteCode
Asm 官网地址asm.ow2.org/index.html
我们接着讲 Delegate Property 的 gen 过程. 以address1为例

  1. var address1: kotlin.String by kotlin.properties.Delegates.observable(address) { _, old, new ->
  2. kotlin.io.println("oldValue = $old And newValue = $new")
  3. }

observable的代码如下

  1. /**
  2. * Returns a property delegate for a read/write property that calls a specified callback function when changed.
  3. * @param initialValue the initial value of the property.
  4. * @param onChange the callback which is called after the change of the property is made. The value of the property
  5. * has already been changed when this callback is invoked.
  6. */
  7. public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
  8. ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
  9. override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
  10. }

address1是具有Delegate 属性的, 所以走的是generatePropertyDelegateAccess分支. 那么我们 debug 一下看看
从编译层面来了解Kotlin(一) - 图2
从编译层面来了解Kotlin(一) - 图3
从上两张图可以看到. 在最终生成bytecode 中, address1 变成了address1$delegate, 而且类型也变成了ReadWriteProperty. 但address1gettersetter 还是getAddress1,setAddress1.
所以Delegate大致的工作原理是这样的
从编译层面来了解Kotlin(一) - 图4
Lazy 也是 Delegate 中的一种, 只不过它只能修饰val 属性, 不存在 setter方法. 所以可以理解 lazy 的 val Property 是一个懒加载的常量. 如图所示, Lazy属性 在语法层面上是无法设置 setter, 和 getter
从编译层面来了解Kotlin(一) - 图5

2.1.2 genAnnotations

在 java 里, 主要是添加@ NotNull 以及@ Nullable annotation

2.1.3 getter And setter

先看 setter

  1. private void generateSetter(@Nullable KtNamedDeclaration p, @NotNull PropertyDescriptor descriptor, @Nullable KtPropertyAccessor setter) {
  2. if (!descriptor.isVar()) return;
  3. generateAccessor(p, setter, descriptor.getSetter() != null
  4. ? descriptor.getSetter()
  5. : DescriptorFactory.createDefaultSetter(descriptor, Annotations.Companion.getEMPTY()));
  6. }

先会判断是否是var, 如果不是就直接返回, 所以val的属性不会生成 setter, 通过这种方式kotlin语言就达到了无法修改valProperty的目的.
接着在生成 setter 之前, 它会先判断开发者有否有自定义的 setter. 如果有就用之,没有就创建一个默认的

  1. var zip: Int? = null
  2. set(value) {
  3. field = value
  4. }

这些方法最终也是通过asm来直接生成的. 这里有个细节, 针对非空的入参Property 都会在当前函数的字节码的最前方插入非空的判断函数(static 方法).

  1. private static void genParamAssertion(
  2. @NotNull InstructionAdapter v,
  3. @NotNull KotlinTypeMapper typeMapper,
  4. @NotNull FrameMap frameMap,
  5. @NotNull CallableDescriptor parameter,
  6. @NotNull String name
  7. ) {
  8. KotlinType type = parameter.getReturnType();
  9. if (type == null || isNullableType(type)) return;
  10. int index = frameMap.getIndex(parameter);
  11. Type asmType = typeMapper.mapType(type);
  12. if (asmType.getSort() == Type.OBJECT || asmType.getSort() == Type.ARRAY) {
  13. v.load(index, asmType);
  14. v.visitLdcInsn(name);
  15. v.invokestatic(IntrinsicMethods.INTRINSICS_CLASS_NAME, "checkParameterIsNotNull",
  16. "(Ljava/lang/Object;Ljava/lang/String;)V", false);
  17. }
  18. }
  1. public static void checkParameterIsNotNull(Object value, String paramName) {
  2. if (value == null) {
  3. throwParameterIsNullException(paramName);
  4. }
  5. }

这就是 kotlin 能够避免空指针的原因. 它帮你做了该做的非空判断.

2.2 data class

kotlin data class, 例如 data class User(val name: String, val age: Int)
简简单单的一行代码同等同于java差不多50行吧.. 那么它是怎么做到的呢.
下面这段代码是一个类基本的构造步骤, 从代码字面意思就能看出每个函数所干的事情. 而且结构很整洁..

  1. public void generate() {
  2. generateDeclaration();
  3. boolean shouldGenerateSyntheticParts =
  4. !(element instanceof KtClassOrObject) ||
  5. state.getGenerateDeclaredClassFilter().shouldGenerateClassMembers((KtClassOrObject) element);
  6. if (shouldGenerateSyntheticParts) {
  7. generateSyntheticPartsBeforeBody();
  8. }
  9. generateBody();
  10. if (shouldGenerateSyntheticParts) {
  11. generateSyntheticPartsAfterBody();
  12. }
  13. if (state.getClassBuilderMode().generateMetadata) {
  14. generateKotlinMetadataAnnotation();
  15. }
  16. done();
  17. }

那些自动生成的方法的入口函数是generateSyntheticPartsAfterBody, 进入这个函数你就会发现Data Class生成了哪些方法. 不出意外你也会了解到Enmu Class为什么会有 valuesvalueof方法..

  1. fun generate() {
  2. generateComponentFunctionsForDataClasses()
  3. generateCopyFunctionForDataClasses(primaryConstructorParameters)
  4. val properties = dataProperties
  5. if (properties.isNotEmpty()) {
  6. generateDataClassToStringIfNeeded(properties)
  7. generateDataClassHashCodeIfNeeded(properties)
  8. generateDataClassEqualsIfNeeded(properties)
  9. }
  10. }

上面这段代码就是 Data class 收尾阶段生成ComponentX, toString等方法的地方.
所以, 它之所以这么便捷, 是因为编译器帮你写了剩下的行代码..

2.3 Extension Functions
  1. fun String.SayHello() {
  2. println("Hello From $this")
  3. }

debug 看下这个函数是如何生成的.
从编译层面来了解Kotlin(一) - 图6
可以看到

  1. Function 的字节码asmMethodSayHello(Ljava/lang/String;)V
  2. Flag 是25, 25对应的是ACC_PUBLIC ACC_FINAL ACC_STATIC

那么这个函数对应的 java方法应该是这样

  1. public static final void SayHello(@NotNull String $receiver) {
  2. Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
  3. String var1 = "Hello From " + $receiver;
  4. System.out.println(var1);
  5. }

所以XXX.fun() 之类的定义, 在转换成字节码之后, 都是一个 public static final 的方法, 而且第一个参数是XXX类型

2.4 Higher-Order Functions and Lambdas

例如这个例子

  1. fun <T> lock(lock: Lock?, body: () -> T): T {
  2. lock?.lock()
  3. try {
  4. return body()
  5. } finally {
  6. lock?.unlock()
  7. }
  8. }

lock 方法接收两个参数,1. Lock, 2 () -> T 的方法.
在 kotlin 里, 如果一个函数最后一个参数是一个 Function, 那么就可以写成 Lambda 的方式.
所以我们可以这样调用 lock

  1. fun callLock() {
  2. lock(null) {
  3. println("")
  4. }
  5. }

body: () -> T 最终转换成字节码对应的东西是一个Function0的对象.
从编译层面来了解Kotlin(一) - 图7
如图所示, body 接收几个对象, 对应的就是 FunctionX. 目前 kotlin 最多支持22个参数的入参函数
所以上述函数这么用也是合法的

  1. fun callLock1() {
  2. lock(null, object : Function0<Unit> {
  3. override fun invoke(): Unit {
  4. println("")
  5. }
  6. })
  7. }

暂时就介绍这几个常用特性的编译吧.
Read the Fucking Source Code