Kotlin + Spring でインジェクトされるオブジェクトがプロキシ(CGLib)を伴っていた場合、メソッドがprivate変数をデフォルト非null引数に使っていると死ぬ

タイトルどおり。

条件が揃うと、一見正しそうなKotlin + Springのコードが死にます。

## 再現コード

再現コードは下記にあります。

https://github.com/knjname/2019-05-22_springkotlinpitfall

```bash
$ git clone https://github.com/knjname/2019-05-22_springkotlinpitfall 2019-05-22_springkotlinpitfall
$ cd !$
$ ./gradlew bootRun

...
java.lang.IllegalStateException: Failed to execute CommandLineRunner
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:816) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
        at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:797) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:324) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
        at knjname.springkotlinpitfall.SpringkotlinpitfallApplicationKt.main(SpringkotlinpitfallApplication.kt:41) ~[main/:na]
Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method knjname.springkotlinpitfall.Nuke.withOptional, parameter lst
        at knjname.springkotlinpitfall.Nuke.withOptional(SpringkotlinpitfallApplication.kt) ~[main/:na]
        at knjname.springkotlinpitfall.Nuke$$FastClassBySpringCGLIB$$13faa123.invoke(<generated>) ~[main/:na]
...

```

## 解説

下記コードが問題を再現させるコードです。

```kotlin
package knjname.springkotlinpitfall

import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional

@Component
@SpringBootApplication
class SpringkotlinpitfallApplication(
        private val nuke: Nuke
) : CommandLineRunner {

    override fun run(vararg args: String?) {
        // ここから起動します
        nuke.withOptional()
    }
}

@Component
class Nuke {

    private val a = listOf("a")

    // ここで死ぬ！！
    // Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null:
    // method knjname.springkotlinpitfall.Nuke.withOptional, parameter lst
    fun withOptional(
            lst: List<Any> = a
    ) = Unit

    @Transactional
    fun xxx() = Unit

}


fun main(args: Array<String>) {
    runApplication<SpringkotlinpitfallApplication>(*args)
}

```

上記を実行すると、 `withOptional` メソッドの呼び出しにて、Kotlinのnullチェックに引っかかって例外で死にます。

下記条件が揃っているためです。

- `@Transactional` を持つことにより、 `Nuke` クラスはCGLibでラップされることとなる。
    - Springで注入された `Nuke` インスタンスは全てCGLibのプロキシクラスになる。
    - CGLib でラップされたクラスのフィールド変数は `null` となる。
- Kotlinがデフォルト引数つきのメソッドを呼ぶ際は `Nuke` クラスにヘルパ用の static メソッド (`withOptional$default`) を生成し、そのメソッドに `Nuke`のインスタンス（今回はCGLibにラッパされたインスタンス） を引数として渡して呼び出す。
- 該当のstaticメソッドは `Nuke.a` をデフォルト引数の値とするため参照するが、CGLibにラップされたクラスのフィールドであるため、上述の通り、`null` 値が入る。 
    - つまり、 `listOf("a")` は使われない。
- `null` 値がKotlinのランタイム時の `withOptional` メソッド内のnullチェックによってチェックされ、例外が発生する。

上記のように分かりづらい経緯を経て死にます。デフォルト引数怖いっ！

## ワークアラウンド

上記条件にひっかからなければ回避可能なので、下記のような回避を取れるでしょう。

- デフォルト引数に指定する定数を
    - `private val` ではなく `val` を用いる
    - クラス内に宣言しない
    - class の外に宣言する
    - コンパニオンオブジェクトに宣言する