基于字节码指令分析 Kotlin 代理

817 阅读4分钟

基于字节码分析 Kotlin 代理

我之前通过分析字节码分析过 Kotlin 的内联函数的实现,感兴趣的可以参考:通过看字节码指令扒光 Kotlin 内联函数的底裤,如果不熟悉 JVM 字节码的同学还可以参考一下这篇文章:JVM 字节码
回到正题,Kotlin 中的代理分为两种:接口实现代理和变量代理(我也不知道该叫什么名字,这个名字是我自己起的😂)。我就分别来介绍一下这两种代理,这两种代理都是会用到关键字 by

接口实现代理

假如我有以下的接口:

package com.tans.test

interface IFoo {
    fun foo()
}

然后实现的类如下:

package com.tans.test

class FooImpl : IFoo {

    override fun foo() {
        println("Invoke foo.")
    }
}

假如我有一个 Main 的类也需要实现 IFoo 接口,但是接口的实现用的是 FooImpl,那按所有人都能想到的思维逻辑写法就是:

package com.tans.test


class Main : IFoo {
    
    private val fooImpl = FooImpl()

    override fun foo() {
        fooImpl.foo()
    }
}

我们的这个接口只有一个方法,其实也还行,如果这个接口有 20 个方法呢?在开发过程中不是要在这个源代码中加 20 个? 那不是要添加好多这样的模版代码,而且还容易添加错,也不易于源码的阅读。
Kotlin 早就为我们想到了这种场景,然后上面的代码可以通过代理的方式来处理:

package com.tans.test


class Main : IFoo by FooImpl() {
    
}

是不是代码瞬间清爽了。不愧是 KotlinMake developers happy.

然后我们来看看它是怎么实现的呢?

Main 类的构造函数字节码指令:

 0 aload_0
 1 invokespecial #10 <java/lang/Object.<init> : ()V>
 4 aload_0
 5 new #12 <com/tans/test/FooImpl>
 8 dup
 9 invokespecial #13 <com/tans/test/FooImpl.<init> : ()V>
12 putfield #17 <com/tans/test/Main.$$delegate_0 : Lcom/tans/test/FooImpl;>
15 return

首先调用了 Object<init> 函数,然后又创建了一个 FooImpl 对象,然后赋值到成员变量 $$delegate_0 中。

然后我们再看看 Main#foo() 方法的字节码指令:

0 aload_0
1 getfield #17 <com/tans/test/Main.$$delegate_0 : Lcom/tans/test/FooImpl;>
4 invokevirtual #22 <com/tans/test/FooImpl.foo : ()V>
7 return

也是非常的简单直接调用成员变量 $$delegate_0foo() 方法。

这个代理的实现可以说和我们自己写是完全一样的。不过通过 by 代理的这种方式,让代码更加清爽,不容易出错,也更容易阅读。

变量代理

变量代理的代理实现可以自定义,如果是仅仅是可读的变量,那代理实现要通过实现 ReadOnlyProperty 接口。如果是可读可写的代理实现那就要实现 ReadWriteProperty 接口。在 Delegates 还有很多默认的实现,读者可以自己去看一下,我就不多说了。在我自己的开发中用得最多的就是 lazy 方法生成的代理对象,我也以 lazy 来做本次的分析,其他的代理也都是类似的。

假如有一个场景:有一个对象我想要在它使用的时候才去初始化,没有使用就不初始化,如果拿去的时候已经初始化了就用上次已经初始化了的对象。如果要用 Java 写的话,又要 balabala 写一大堆。不过用 Kotlin 写的话就非常简单,这需要把这个对象代理给 lazy 就好了。

package com.tans.test

class Main {

    private val foo: IFoo by lazy {
        FooImpl()
    }

    fun foo() {
        foo.foo()
    }
}

其中 lazy() 方法就会返回一个代理对象,lambda 中返回的就是构建我们需要的对象实例,它只会执行一次。

ok 我们来看看它的字节码。

proxy.png

首先一来就是当头一棒,我们的成员变量 foo 消失了,只剩一个 foo$delegate 成员变量,然后他的类型是 Lkotlin/Lazy;

然后我们看看构造函数的指令:

 0 aload_0
 1 invokespecial #8 <java/lang/Object.<init> : ()V>
 4 aload_0
 5 getstatic #14 <com/tans/test/Main$foo$2.INSTANCE : Lcom/tans/test/Main$foo$2;>
 8 checkcast #16 <kotlin/jvm/functions/Function0>
11 invokestatic #22 <kotlin/LazyKt.lazy : (Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy;>
14 putfield #26 <com/tans/test/Main.foo$delegate : Lkotlin/Lazy;>
17 return

在构造函数中调用 lazy 方法创建一个 Lkotlin/Lazy 对象,然后赋值给成员变量 Main.foo$delegate,其中还有一个 lambda 参数 Main$foo$2.INSTANCE,这也就是我们构造 FooImpl 实例的 lambda

然后我们再去看看这个 lambdainvoke 函数。

0 new #19 <com/tans/test/FooImpl>
3 dup
4 invokespecial #21 <com/tans/test/FooImpl.<init> : ()V>
7 areturn

朴实无华,直接创建一个 FooImpl 对象。

然后继续看看 Main#foo() 方法的字节码指令:

0 aload_0
1 invokespecial #41 <com/tans/test/Main.getFoo : ()Lcom/tans/test/IFoo;>
4 invokeinterface #43 <com/tans/test/IFoo.foo : ()V> count 1
9 return

然后通过 Main#getFoo()(其实这就是常说的 getter 函数) 方法获取 IFoo 实例,然后调用 foo() 方法。

继续看看 Main#getFoo() 方法的字节码指令:

 0 aload_0
 1 getfield #26 <com/tans/test/Main.foo$delegate : Lkotlin/Lazy;>
 4 astore_1
 5 iconst_0
 6 istore_3
 7 aload_1
 8 invokeinterface #36 <kotlin/Lazy.getValue : ()Ljava/lang/Object;> count 1
13 checkcast #38 <com/tans/test/IFoo>
16 areturn

这里其实就调用了 Lazy#getValue() 方法来获取 IFoo 对象,然后返回。

我们自己的代码中的字节码指令就看完了,最终获取我们的 IFoo 对象是通过 Lazy#getValue() 方法来获取的。然后我们就看看 lazy 方法的源码实现了。

public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

我们这里的 Lazy 对象的实现是 SynchronizedLazyImpl

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    // final field is required to enable safe publication of constructed instance
    private val lock = lock ?: this

    override val value: T
        get() {
            val _v1 = _value
            if (_v1 !== UNINITIALIZED_VALUE) {
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }

            return synchronized(lock) {
                val _v2 = _value
                if (_v2 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                } else {
                    val typedValue = initializer!!()
                    _value = typedValue
                    initializer = null
                    typedValue
                }
            }
        }

    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE

    override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."

    private fun writeReplace(): Any = InitializedLazyImpl(value)
}

其实 valueget() 函数就是上面字节码指令中的 getValue() 方法。我们对象真实的值是存储在 _value 变量中,如果 _value 不等于 UNINITIALIZED_VALUE 就表示已经初始化了,直接返回就好,如果没有初始化就调用我们传递过来的 lambda 完成初始化,也就是 initializer 对象,然后保存到 _value 中供以后的调用使用。
这里只是 lazy 代理的 getValue() 实现,不同的代理类都是有不同的实现,感兴趣的可以去看看其他的默认实现。

最后

希望这篇文章能够让你更加理解 Kotlin 中的代理,基于它的原理让你能够更好的使用 Kotlin 代理。