LoginSignup
1
0

More than 3 years have passed since last update.

Minecraft Spigot PluginにKoinを導入する

Posted at

背景

プラグイン開発において、pluginのインスタンスや、serverのインスタンスの呼び出しや参照は頻繁に使います。

メタデータでのJavaPluginの参照

fun setMetadata(player: Player, plugin: JavaPlugin, key: String, value: Boolean) {
    player.setMetadata(key, FixedMetadataValue(plugin, value))
}

プレイヤーの取得

class PlayerListener(val plugin: JavaPlugin) : Listener {
    @EventHandler
    fun onPlayerJoin(event: PlayerJoinEvent) {
        val players = plugin.server.onlinePlayers
    }
}
class Game(val plugin: JavaPlugin) {
    fun start() {
        val players = plugin.server.onlinePlayers
    }
}

といったように、pluginインスタンスをバケツリレーのようにして受け渡すことが多いです。

既存の解決策

シングルトンとして定義する方法

SamplePlugin.kt
class SamplePlugin : JavaPlugin() {

    companion object {
        lateinit var INSTANCE: JavaPlugin
            private set
    }

    override fun onEnable() {
        PLUGIN = this
    }
}

// 参照するとき
fun example() {
    val plugin = SamplePlugin.INSTANCE
}

上記のように定義して、コードのどの位置からでも参照できる方法があります。
この方法でも大きな問題はなさそうですが、以下のような課題点があります。

  • pluginのインスタンスに対して再代入が起きる可能性がある
  • テストが難しくなる
  • コードとしての見通しが悪くなる

Koinの導入

Spigotプラグインのpluginやserverといったインスタンスの参照をKoinを使ってわかりやすく記述することができます。
一般的に、Dependency Injectionという方法になります。

build.gradleに以下を追加して記述します。

build.gradle
buildscript {
    ext {
        koin_version = '2.1.6'
    }
    repositories {
        jcenter()
    }
    dependencies {
        classpath "org.koin:koin-gradle-plugin:$koin_version"
    }
}

apply plugin: 'koin'

dependencies {
    compile "org.koin:koin-core:$koin_version"
    compile "org.koin:koin-core-ext:$koin_version"
    testCompile "org.koin:koin-test:$koin_version"
}

JavaPluginを以下のように書きます。

SamplePlugin.kt
class SamplePlugin : JavaPlugin() {
    override fun onEnable() {
        // .....

        setupKoin()
    }

    private val myModule = module {
        single<JavaPlugin> { this@SamplePlugin }
        single { server }
    }

    private fun setupKoin() {
        startKoin {
            modules(listOf(myModule))
        }
    }
}

myModuleで定義したものは、以下のようなKoinComponentを継承したクラスで参照できます。

Game.kt
class Game : KoinComponent {
    private val plugin: JavaPlugin by inject()
    private val server: Server by inject()

    fun start() {
        val players = server.onlinePlayers
    }
}

また、自分で作成したクラスもKoinのmoduleに含めることができます。

PluginPreference.kt
class PluginPreference(private val plugin: JavaPlugin) {
    // .....
}
SamplePlugin.kt
class SamplePlugin : JavaPlugin() {
    override fun onEnable() {
        // .....

        setupKoin()
    }

    private val myModule = module {
        single<JavaPlugin> { this@SamplePlugin }
        single { server }
        single { PluginPreference(get()) }
    }

    private fun setupKoin() {
        startKoin {
            modules(listOf(myModule))
        }
    }
}

get()を使うことで、Koinで定義したオブジェクトを参照することができます。

テストの記述

テストを行うときは、MockKで生成したモックオブジェクトをKoinに入れることができます。
これによってテストを行いやすくなります。

GameTest.kt
internal class GameTest {
    companion object {
        private val plugin = mockk<JavaPlugin>()
        private val server = mockk<Server>()

        private val mockModule: Module = module {
            single { plugin }
            single { server }
        }

        @BeforeAll
        @JvmStatic
        internal fun beforeAll() {
            startKoin {
                modules(listOf(mockModule))
            }
        }
    }

    @Test
    fun result() {
        // テストを記述する
        val game = Game()
        Assertions.assertEquals()
    }
}

終わりに

KoinやDIコンテナ、依存性の注入については他にも詳しい解説があるので、調べてみてください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0