Войти или зарегистрироваться
  • Вы находитесь в сообществе Rubukkit. Мы - администраторы серверов Minecraft, разрабатываем собственные плагины и переводим на различные языки плагины наших коллег из других стран.

Стартап Туториал | Внедрение зависимостей в плагинах через Dagger 2

SashaBrine

Активный участник
Пользователь
Баллы
76
Dagger 2 - это инструмент, предоставляющий возможность внедрения зависимостей в Java/Kotlin-приложения. Внедрение зависимостей (Dependency Injection, DI) - это шаблон проектирования, позволяющий уменьшить связность между объектами и упростить тестирование кода.

Начнем с базовой части. Создадим наш проект с таким билд-файлом:
Код:
plugins {
    java
}

group = "me.fortibrine"
version = "1.0"

repositories {
    mavenCentral()

    maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
    maven("https://oss.sonatype.org/content/repositories/central")
}

dependencies {
    compileOnly("org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT")

    implementation("com.google.dagger:dagger:2.47")
    annotationProcessor("com.google.dagger:dagger-compiler:2.47")
}

tasks {
    withType<JavaCompile>().configureEach { options.encoding = "UTF-8" }

    jar {

        duplicatesStrategy = DuplicatesStrategy.EXCLUDE
        from (
            configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }
        )

    }
}

Далее мы реализуем код для выполнения внедрения. Более конкретно, мы создадим:

  • модуль, который представляет собой класс, предоставляющий или создающий зависимости объектов, и
  • компонент, который представляет собой интерфейс, используемый для генерации инжектора.
Сложные проекты могут содержать несколько модулей и компонентов, но поскольку мы имеем дело с очень простым плагином, одного компонента будет достаточно.

Итак, создадим наш компонент. Я создаю как правило структуру проекта в таком виде - com.example.plugin.di.component. Создадим там наш компонент.
Код:
package me.fortibrine.daggersample.di.component;

import dagger.BindsInstance;
import dagger.Component;
import me.fortibrine.daggersample.DaggerSamplePlugin;

import javax.inject.Singleton;

@Singleton
@Component
public interface PluginComponent {
    public void inject(DaggerSamplePlugin plugin);

    @Component.Factory
    public interface Factory {
        public PluginComponent create(@BindsInstance DaggerSamplePlugin plugin);
    }
}

Сам Dagger имеет механизм обработки аннотаций и генерации кода. Поэтому он с нашего интерфейса сгенерирует DaggerPluginComponent котоырй мы будем использовать в нашем главном классе:
Код:
package me.fortibrine.daggersample;

import me.fortibrine.daggersample.di.component.DaggerPluginComponent;
import org.bukkit.plugin.java.JavaPlugin;

public class DaggerSamplePlugin extends JavaPlugin {

    public void onEnable() {
        DaggerPluginComponent.factory()
                .create(this)
                .inject(this);


    }

}

Теперь хочу вам показать внедрение зависимостей на примере слушателей евентов. Я предлагаю создавать класс Listeners в котормо и будут наши слушатели. Вот пример создания:

Код:
package me.fortibrine.daggersample.listener;

import me.fortibrine.daggersample.DaggerSamplePlugin;
import org.bukkit.Bukkit;

import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
public class Listeners {

    @Inject
    public Listeners(DaggerSamplePlugin plugin, SampleListener sampleListener) {
        Bukkit.getPluginManager().registerEvents(sampleListener, plugin);
    }

}

Dagger сам достаёт экземпляр класса DaggerSamplePlugin и SampleListener и даёт нам.

Код:
package me.fortibrine.daggersample.listener;

import me.fortibrine.daggersample.DaggerSamplePlugin;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;

import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
public class SampleListener implements Listener {

    private final DaggerSamplePlugin plugin;

    @Inject
    public SampleListener(DaggerSamplePlugin plugin) {
        this.plugin = plugin;
        plugin.getLogger().info("SampleListener registered");
    }

    @EventHandler
    public void onJoin(PlayerJoinEvent event) {
        event.setJoinMessage(null);
        plugin.getServer().broadcastMessage(event.getPlayer().getName() + " joined!");
    }

}

Dagger передаёт нашему классу экземпляр класса DaggerSamplePlugin
Ещё хочу сказать что нужно внедрить Listeners в наш плагин, иначе Dagger не сможет понять что нужно инжектить класс Listeners. Dagger не Spring, он работаёт по цепочке.

Код:
package me.fortibrine.daggersample;

import me.fortibrine.daggersample.di.component.DaggerPluginComponent;
import me.fortibrine.daggersample.listener.Listeners;
import org.bukkit.plugin.java.JavaPlugin;

import javax.inject.Inject;

public class DaggerSamplePlugin extends JavaPlugin {

    @Inject
    Listeners listeners;

    public void onEnable() {
        DaggerPluginComponent.factory()
                .create(this)
                .inject(this);


    }

}

Давайте внедрим ещё какую-то зависимость в наш слушатель. Создадим класс BanManager например:
Код:
package me.fortibrine.daggersample.utils;

import me.fortibrine.daggersample.DaggerSamplePlugin;
import org.bukkit.BanList;
import org.bukkit.entity.Player;

import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
public class BanManager {

    private DaggerSamplePlugin plugin;

    @Inject
    public BanManager(DaggerSamplePlugin plugin) {
        this.plugin = plugin;
    }

    public void banPlayer(Player player) {
        plugin.getServer().getBanList(BanList.Type.NAME).addBan(player.getName(), "Rules", null, "Console");
    }
    
    public void unbanPlayer(Player player) {
        plugin.getServer().getBanList(BanList.Type.NAME).pardon(player.getName());
    }

}

Внедрим в наш слушатель:
Код:
package me.fortibrine.daggersample.listener;

import me.fortibrine.daggersample.DaggerSamplePlugin;
import me.fortibrine.daggersample.utils.BanManager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerJoinEvent;

import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
public class SampleListener implements Listener {

    private final DaggerSamplePlugin plugin;
    private final BanManager banManager;

    @Inject
    public SampleListener(DaggerSamplePlugin plugin, BanManager banManager) {
        this.plugin = plugin;
        this.banManager = banManager;
        plugin.getLogger().info("SampleListener registered");
    }
    
    @EventHandler
    public void onKill(PlayerDeathEvent event) {
        if (event.getEntity().getKiller() != null) {
            banManager.banPlayer(event.getEntity().getKiller());
        }
    }

    @EventHandler
    public void onJoin(PlayerJoinEvent event) {
        event.setJoinMessage(null);
        plugin.getServer().broadcastMessage(event.getPlayer().getName() + " joined!");
    }

}

Dagger создаст нашу утилиту и передаст слушателю.
Теперь нужно понять как работают модули и как их использовать. Создадим самый простой модуль
Код:
package me.fortibrine.daggersample.di.module;

import dagger.Module;
import dagger.Provides;
import me.fortibrine.daggersample.DaggerSamplePlugin;
import org.bukkit.Server;

import javax.inject.Singleton;

@Module
public class SampleModule {

    @Singleton
    @Provides
    public Server provideServer(DaggerSamplePlugin plugin) {
        return plugin.getServer();
    }

}
Нужно нашему компоненту сказать что мы хотим использовать наш модуль:
Код:
package me.fortibrine.daggersample.di.component;

import dagger.BindsInstance;
import dagger.Component;
import me.fortibrine.daggersample.DaggerSamplePlugin;
import me.fortibrine.daggersample.di.module.SampleModule;

import javax.inject.Singleton;

@Singleton
@Component(modules = {SampleModule.class})
public interface PluginComponent {
    public void inject(DaggerSamplePlugin plugin);

    @Component.Factory
    public interface Factory {
        public PluginComponent create(@BindsInstance DaggerSamplePlugin plugin);
    }
}

Объясняю: в связи с тем, что у Server нет ни конструктора с аннотацией @Inject, ни провайдера, при попытке внедрить зависимость возникнет ошибка. Мы создали метод-провайдер, и теперь ошибки не будет.

Сам туториал я создал, потому что в интернете нет гайдов по работе с Spigot и Dagger. Сейчас существуют и другие DI-библиотеки, такие как Google Guice или Spring Framework. Dagger считается самой производительной.
 

Поделиться этой страницей

Назад
Верх