Lekce 10 - Java server - Systém pluginů
V minulé lekci, Java server - Event bus, jsme se věnovali propagaci událostí napříč serverem pomocí event bus.
Dnes navrhneme a implementujeme systém, pomocí kterého budeme moci snadno rozšiřovat funkcionalitu serveru.
Plugin
Na úvod by bylo dobré zadefinovat, co to plugin vlastně je. Plugin bude představovat jednotlivé funkcionality serveru. Pod termínem funkcionalita serveru si můžete představit:
- správa uživatelů
- přístup k databázi
- chat
- komunikace s externí službou
- a další...
Návrh systému pluginů
Náš pluginový systém bude relativně jednoduchý. Bude mít za úkol při startu serveru načíst všechny dostupné pluginy a inicializovat je. V budoucnu doplníme tento systém o načítání pluginů z externích jar souborů, abychom nemuseli server překompilovat pokaždé, když bude někdo chtít přidat novou funkci.
Implementace
Implementaci začneme návrhem rozhraní, které bude reprezentovat plugin.
Rozhraní nazveme IPlugin
a vložíme ho do
samostatného balíčku plugins
. Rozhraní bude
pro začátek obsahovat metodu getName()
, která jak již název
napovídá vrátí název pluginu. Dále metodu init()
, ve které
se bude plugin inicializovat. Pomocí metody
registerMessageHandlers()
bude moci plugin zaregistrovat všechny
události, na které bude v budoucnu reagovat. V metodě
setupDependencies()
bude možné propojit jednotlivé pluginy
dohromady. Celé rozhraní vypadá následovně:
package cz.stechy.chat.plugins; public interface IPlugin { String getName(); void init(); void registerMessageHandlers(IEventBus eventBus); void setupDependencies(Map<String, IPlugin> otherPlugins); }
Ve stejném balíčku vytvoříme výčet, který bude obsahovat veškeré
pluginy, které budou zakomponovány přímo v serveru. Výčet nazvěme
jednoduše Plugin
. Výčet bude obsahovat
konstantu typu Class<? extends IPlugin>
. Nezapomeňte na
konstruktor:
public enum Plugin { ; public final Class<? extends IPlugin> clazz; Plugin(Class<? extends IPlugin> clazz) { this.clazz = clazz; } }
Tato konstanta bude odkazovat na třídu, která implementuje plugin. Prozatím je výčet prázdný, proto musí být na začátku přítomen středník.
Nyní zaregistrujeme všechny moduly do Google guice, aby nám je automaticky
instancioval. Vytvoříme nový modul
PluginModule
, který bude dědit ze třídy
AbstractModule
, kterou nám poskytuje knihovna
guice a implementujeme metodu configure:
public class PluginModule extends AbstractModule { @Override protected void configure() { MapBinder < String, IPlugin > pluginBinder = MapBinder.newMapBinder(binder(), String.class, IPlugin.class); for (Plugin plugin: Plugin.values()) { pluginBinder.addBinding(plugin.name()).to(plugin.clazz).asEagerSingleton(); } // TODO načíst externí pluginy } }
V této metodě si připravíme proměnnou pluginBinder
, pomocí
které spárujeme implementace všech pluginů s naším rozhraním
IPlugin
. Více o implementaci
MapBinder
najdete na wiki guice.
Spárování provedeme ve smyčce, ve které budeme iterovat přes výčet
pluginů, které jsou přímo na serveru. Za zmínku už stojí jen volání
metody asEagerSingleton()
, která říká, že kdykoliv zažádáme
o konkrétní instanci pluginu, pokaždé dostaneme tu samou. Načtení
externích pluginů si necháme na později.
Dále zaregistrujeme nově vytvořený modul do guice. Ve třídě
Server
, v metodě main()
, kde se
vytváří instance třídy Injector
, přidáme
nový modul PluginModul
:
final Injector injector = Guice.createInjector(new ServerModule(), new PluginModule());
Nakonec upravíme třídu Server
. Přidáme konstantu typu
Map<String, IPlugin>
a konstantu typu IEventBus
.
Tyto konstanty bude třída přijímat v konstruktoru:
@Inject public Server(IParameterFactory parameterFactory, IServerThreadFactory serverThreadFactory, IEventBus eventBus, Map<String, IPlugin> plugins) { this.parameterFactory = parameterFactory; this.serverThreadFactory = serverThreadFactory; this.eventBus = eventBus; this.plugins = plugins; }
Nyní již zbývá postarat se o inicializaci pluginů. Za tímto účelem si
vytvoříme privátní metodu initPlugins()
, ve které se
postaráme o inicializaci:
private void initPlugins() { for (IPlugin plugin: plugins.values()) { plugin.init(); } for (IPlugin plugin: plugins.values()) { plugin.registerMessageHandlers(messageRegistrator); } for (IPlugin plugin: plugins.values()) { plugin.setupDependencies(plugins); } }
Nejdříve zavoláme nad všemi pluginy metodu init()
a dáme
jim možnost se inicializovat. Dále přejdeme do fáze registrace posluchačů
na příslušné události a nakonec necháme pluginy, aby si nastavily své
závislosti mezi sebou. Metodu initPlugins()
budeme volat před
startem vlákna serveru.
Použití
Pro demonstrační účely vytvoříme jednoduchý plugin, který nebude
dělat nic, pouze ve fázi inicializace vypíše svůj název do konzole. V
balíčku plugins vytvoříme další balíček s názvem hello
. V
tomto balíčku vytvoříme třídu HelloPlugin
, která bude
implementovat naše rozhraní IPlugin
. Požadované metody
implementujeme následovně:
package cz.stechy.chat.plugins.hello; public class HelloPlugin implements IPlugin { @Override public String getName() { return "HelloPlugin"; } @Override public void init() { System.out.println("Inicializace pluginu: " + getName()); } }
Záznam o pluginu přidáme do výčtu pluginů. Nezapomeňte, že nestačí přidat pouze název, ale i třídu, která implementuje daný plugin:
public enum Plugin { HELLO(HelloPlugin.class); ... }
To je vše. Když spustíte server, dostanete hlášku, že se plugin inicializoval.
To by bylo z dnešní lekce vše.
V následujícím kvízu, Kvíz - Komunikační protokol, Event bus a pluginy v Javě, si vyzkoušíme nabyté zkušenosti z předchozích lekcí.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 23x (288.88 kB)
Aplikace je včetně zdrojových kódů v jazyce Java