When you start your application, Botrino automatically loads all the classes present in bot modules. According to the type of classes that are discovered, an action will be performed on them such as registering a service or adding a configuration entry. Extensions allow you to hook into this module scanning process to add your own logic when classes are loaded.
Unlike other components of the framework, extensions do not need to reside in a module annotated with
@BotModule. Think of extensions like plugins for the framework itself and not for your bot application directly. Your module does not need to be
open either, extensions are loaded via
The first step is to create a class implementing the
Before going into the details of the methods to implement, let's register this class as a provider for
BotrinoExtension. This is done via the
You don't have to create a separate module just for your extension. It is totally fine to add the
provides directive directly in your
@BotModule, this example just shows that you are not required to.
Let's review each of the methods of
BotrinoExtension to implement.
void onClassDiscovered(Class<?> clazz)#
This is a callback method invoked each time a class is discovered in a bot module. In most cases, you will check if this class implements a specific interface or is annotated with a specific annotation, and do some processing when it is relevant to do so.
If you intend to create an instance of the class, it is highly recommended to skip classes annotated with
@RdiService from this method, as they are supposed to be instantiated by the RDI container. That's why the
onServiceCreated(Object) method exists.
void onServiceCreated(Object o)#
This is a callback method invoked each time a service is created. It allows to execute some action on the service object right after it's created.
As this method returns
void, it is not suited for performing reactive tasks. Instead, store the service object in a field and perform this task in
Even though the extension may not be inside a bot module, it is still possible to register services that will be exposed to the bot application. You can do so via this method, allowing you to provide a set of RDI service descriptors. This method is only useful if you want to provide complex services that require writing raw descriptors (for example registering a class from a third-party library as a service with a custom name). For simple services maintained by yourself, you can use RDI annotations and make the annotated class discoverable via
provideExtraDiscoverableClasses() instead of doing it via this method.
With this method you can explicitly specify a set of classes that Botrino will pick up just like if they were inside a bot module. It is guaranteed that each class contained in the set will eventually be passed to the
onClassDiscovered(Class) method (unless they have the
@Exclude annotation). As said earlier, it can be used as an alternative way to provide extra services, if the class contained in the set is annotated with RDI annotations. It can also be used to register new configuration entries, or new things you're defining yourself in your own extension!
This is the last method that is invoked during the startup sequence. It allows you to perform a task, possibly reactive, based on the classes and objects you were able to collect via previous invocations of
onServiceCreated(Object). The "join" part of this method's name indicates the fact that the returned reactive sequence does not need to be a finite source: you can use it to start processes living during the entire lifetime of the application, for example installing event listeners or starting a web server. The subscription to the returned
Mono is automatically cancelled once the bot disconnects, allowing the application to shut down properly.
If an exception is thrown or an error is emitted via the
Mono from this method, the exception will propagate to the main thread, which will result in the bot to forcefully disconnect and the application to be terminated.
The interaction library of Botrino provides an implementation of
BotrinoExtension, which is in charge of collecting the classes implementing
InteractionEventProcessor and so on, in order to register them in the
InteractionService. It also exposes a new entry in
config.json that allows to construct the configuration object.
You can check the source code of the extension class of the interaction library on GitHub here. A few things to note to understand the code:
- Classes with the
@RdiServiceannotation are ignored, since we want to use the instance created by RDI in case
InteractionEventProcessorare declared as services.
InstanceCacheis used so that the same instance can be reused in case a class implements more than one interface.
InteractionServiceutilizes RDI annotations, so we provide it via
- All implementations that were found are finally registered in the
finishAndJoin()method, which runs the interaction service at the end.