背景
プラグイン開発において、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インスタンスをバケツリレーのようにして受け渡すことが多いです。
既存の解決策
シングルトンとして定義する方法
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に以下を追加して記述します。
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を以下のように書きます。
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を継承したクラスで参照できます。
class Game : KoinComponent {
private val plugin: JavaPlugin by inject()
private val server: Server by inject()
fun start() {
val players = server.onlinePlayers
}
}
また、自分で作成したクラスもKoinのmoduleに含めることができます。
class PluginPreference(private val plugin: JavaPlugin) {
// .....
}
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に入れることができます。
これによってテストを行いやすくなります。
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コンテナ、依存性の注入については他にも詳しい解説があるので、調べてみてください。