こんにちは。ASKULのほかほかごはんです。 https://www.baeldung.com/kotlin-loggingで紹介されている使い勝手の良いLogger実装 [Logger From an Extension Method] のコードを読んで勉強したことや気づいたことについて記載したいと思います。
KotlinでのLogger実装について
先日、同僚のコードに以下のようなLogger実装を見つけました。
class SomeClass { fun someMethod() { // 2019-04-22 13:35:43,631 [INFO -model.SomeClass] message logger.info("message") } companion object : Logging { private val logger = Logger() } }
うーん、これはかっこいい。今まで一生懸命クラスごとに
LoggerFactory.getLogger(SomeClass::class.java)
を入れ込んでいましたが、これでこのボイラープレートから解消されます。
Logger 実装のコードを読む
確認したところ、この実装は https://www.baeldung.com/kotlin-logging で紹介されているものでした。 さっそく実装を見てみましょう。
interface Logging inline fun <reified T : Logging> T.logger(): Logger = getLogger(getClassForLogging(T::class.java)) fun <T : Any> getClassForLogging(javaClass: Class<T>): Class<*> { return javaClass.enclosingClass?.takeIf { it.kotlin.companionObject?.java == javaClass } ?: javaClass }
なるほど。全然わからん。 そっ閉じしようとも思いましたが、解説やJavadocを頼りになんとか要旨を理解することができました。 元サイトの解説を読むのが一番だと思いますが、簡単にコメントにしておきます。
// マーカーインターフェースを用意する interface Logging // Loggingを実装しているクラスに対する拡張関数を定義する inline fun <reified T : Logging> T.logger(): Logger = getLogger(getClassForLogging(T::class.java)) fun <T : Any> getClassForLogging(javaClass: Class<T>): Class<*> { // 内部クラスのenclosingClassを返す。javaClassがtop level classの場合はnull return javaClass.enclosingClass?.takeIf { // KClassがcompanion objectで引数と同じ場合、enclosingClassを返す it.kotlin.companionObject?.java == javaClass } ?: javaClass // 引数をそのまま返す }
実際にコードを読むことでKotlinのリフレクションについて勉強することができました ;-)
と同時にひとつ疑問が浮かびました。
Companion objectって内部クラスなんでしたっけ?
Companion Objectについて再勉強
今までCompanion Objectをそのクラスに関連のある定数や名前付きconstructorの置き場として利用していたので 所謂ユーティリティクラスだと勝手に誤解したのですが、 改めて公式の説明を見たところしっかり記載がありました。
Companion objectはシングルトンであり、そのメンバには包含クラスを介して直接アクセスできます。
実際にdecompileしてみると、外部クラスはstatic内部クラスとして定義されたCompanion Objectをシングルトンとして利用していることがわかります。
(こちらの記事に詳述されており、参考になりました。)
今回のLogger実装を見たとき、Companion Objectがインターフェースを実装していることを不思議に感じたのですが、
これでスッキリと理解することができました。
まとめ
シンプルで強力なLogger実装のコードリーディングをすることでKotlinのリフレクションを学ぶことができ、 Companion Objectに対しての誤解に気づくことができました。 それではまた次回!