Creating services
To register a class as a service, add @BService
to your class declaration.
@BService
is the base annotation to register a service,
other annotations exist such as @Command
and @Resolver
,
but the appropriate documentation will specify if such alternatives are required.
Info
All classes available for dependency injection must be in the framework's classpath,
by adding packages to BConfigBuilder#packages
, or by using BConfigBuilder#addSearchPath
,
all classes are searched recursively.
Service factories¶
Service factories are methods that create initialized services themselves, they accept other services as parameters and define a service with the method's return type.
In addition to the package requirement,
they must be annotated with @BService
, be in a service, or in an object
, or be a static method.
Terminology
Classes registered as services, and service factories, are service providers.
Example
class Config {
/* */
companion object {
// Service factory, registers as Config (as it is the return type), with the name "config"
// You can use any method name, but the method name is what the service is registered as
@BService
fun config(): Config {
// Of course here you would load the config from a file
Config()
}
}
}
class Config {
/* */
companion object {
// Service factory, registers as Config (as it is the return type), with the name "config"
@get:BService
val config: Config by lazy {
// Of course here you would load the config from a file
Config()
}
}
}
public class Config {
private static Config INSTANCE = null;
/* */
// Service factory, registers as "Config" (as it is the return type), with the name "config"
// You can use any method name, but the method name is what the service is registered as
@BService
public static Config config() {
if (INSTANCE == null) {
// Of course here you would load the config from a file
INSTANCE = new Config();
}
return INSTANCE;
}
}
Tip
To suppress unused warnings on the declaring class, you can use @BConfiguration
,
unless the declaring class itself must be a service.
Conditional services¶
Warning
This section only applies to the built-in dependency injection
Some services may not always be instantiable, some may require soft dependencies (prevents instantiation if a service is unavailable, without failing), while some run a set of conditions to determine if a service can be instantiated.
Services that are not instantiable will not be created at startup, will be unavailable for injection and do not figure in the list of interfaced services.
Info
All the following annotations must be used alongside a service-declaring annotation,
such as @BService
or @Command
.
Tip
There is a few built-in conditional annotations, such as:
@RequiredIntents
: Enables a service if theJDAService
has the specified intents enabled.@RequiresDatabase
: Enables a service if a(Blocking)Database
instance is available.@RequiresComponents
: Enables a service if components are enabled.
Dependencies¶
The @Dependencies
annotation lets you define soft dependencies,
that is, if any of these classes in the annotation are unavailable, your service will not be instantiated.
Without the annotation, any unavailable dependency would throw an exception.
Interfaced conditions¶
@ConditionalService
defines a list of classes implementing ConditionalServiceChecker
,
the service is only created if none of these classes return an error message.
ConditionalServiceChecker
can be implemented on any class that has a no-arg constructor, or is an object
.
Example
@Command
@ConditionalService(TagCommand.FeatureCheck::class) // Only create the command if this passes
class TagCommand {
/* */
object FeatureCheck : ConditionalServiceChecker {
override fun checkServiceAvailability(serviceContainer: ServiceContainer, checkedClass: Class<*>): String? {
val config = serviceContainer.getService<Config>() // Suppose this is your configuration
if (!config.enableTags) {
return "Tags are disabled in the configuration" // Do not allow the tag command!
}
return null // No error message, allow the tag command!
}
}
}
@Command
@ConditionalService(TagCommand.FeatureCheck.class) // Only create the command if this passes
public class TagCommand {
/* */
public static class FeatureCheck implements ConditionalServiceChecker {
@Nullable
@Override
public String checkServiceAvailability(@NotNull ServiceContainer serviceContainer, @NotNull Class<?> checkedClass) {
final var config = serviceContainer.getService(Config.class); // Suppose this is your configuration
if (!config.areTagsEnabled()) {
return "Tags are disabled in the configuration"; // Do not allow the tag command!
}
return null; // No error message, allow the tag command!
}
}
}
Annotation conditions¶
@Condition
is a meta-annotation (an annotation for annotations) which marks your own annotation as being a condition.
Similar to interfaced conditions, they must refer to an implementation of CustomConditionChecker
,
to determine if the annotated service can be created,
you can also indicate if the service creation must throw an exception in case it fails.
The implementation must have a no-arg constructor, or be an object
Note
The annotation must also be in the framework's classpath.
Example
// Same targets as service annotations
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER)
// The implementation of our CustomConditionChecker
@Condition(type = DevCommandChecker::class)
annotation class DevCommand
// Checks services annotated with @DevCommand
object DevCommandChecker : CustomConditionChecker<DevCommand> {
override val annotationType: Class<DevCommand> = DevCommand::class.java
override fun checkServiceAvailability(serviceContainer: ServiceContainer, checkedClass: Class<*>, annotation: DevCommand): String? {
val config = serviceContainer.getService<Config>() // Suppose this is your configuration
if (!config.enableDevMode) {
return "Dev mode is disable in the configuration" // Do not allow the dev commands!
}
return null // No error message, allow the tag command!
}
}
@Command
@DevCommand // Our custom condition, this command will only exist if it passes.
class SlashShutdown {
/* */
}
// Same targets as service annotations
@Target({ElementType.TYPE, ElementType.METHOD})
// The implementation of our CustomConditionChecker
@Condition(type = DevCommandChecker.class)
public @interface DevCommand { }
// Checks services annotated with @DevCommand
public class DevCommandChecker implements CustomConditionChecker<DevCommand> {
@NotNull
@Override
public Class<DevCommand> getAnnotationType() {
return DevCommand.class;
}
@Nullable
@Override
public String checkServiceAvailability(@NotNull ServiceContainer serviceContainer, @NotNull Class<?> checkedClass, @NotNull DevCommand annotation) {
final var config = serviceContainer.getService(Config.class); // Suppose this is your configuration
if (!config.isDevModeEnabled()) {
return "Dev mode is disable in the configuration"; // Do not allow the dev commands!
}
return null; // No error message, allow the tag command!
}
}
@Command
@DevCommand // Our custom condition, this command will only exist if it passes.
public class SlashShutdown {
/* */
}
Interfaced services¶
Warning
This section only applies to the built-in dependency injection
Interfaced services are interfaces, or abstract class, marked by @InterfacedService
,
they must be implemented by a service.
In addition to the service's type, implementations of these annotated interfaces have the interface's type automatically added.
Some interfaced services may only be implemented once, some may allow multiple implementations, if an interfaced service only accepts one implementation, multiple implementations can exist, but only one must be instantiable.
Creating multiple interfaced services in one
You can implement multiple interfaced services at once, which may be useful for text, application and component filters.
Creating interfaced services without registering the interface type
You can also implement an interfaced service, without it being accessible as such,
by using @IgnoreServiceTypes
.
2.X Migration
Most methods in CommandsBuilder
accepting interfaces, implementations or lambdas, were moved to interfaced services:
Global:
CommandsBuilder#setComponentManager
: Removed, using components must be enabled inBComponentsConfigBuilder#enable
, and aConnectionSupplier
service be presentCommandsBuilder#setSettingsProvider
: Needs to implementSettingsProvider
CommandsBuilder#setUncaughtExceptionHandler
: Needs to implementGlobalExceptionHandler
CommandsBuilder#setDefaultEmbedFunction
: Needs to implementDefaultEmbedSupplier
andDefaultEmbedFooterIconSupplier
Text commands:
TextCommandBuilder#addTextFilter
: Needs to implementTextCommandFilter
, andTextCommandRejectionHandler
TextCommandBuilder#setHelpBuilderConsumer
: Needs to implementHelpBuilderConsumer
Application commands:
ApplicationCommandBuilder#addApplicationFilter
: Needs to implementApplicationCommandFilter
, andApplicationCommandRejectionHandler
ApplicationCommandBuilder#addComponentFilter
: Needs to implementComponentCommandFilter
, andComponentCommandRejectionHandler
Extensions:
ExtensionsBuilder#registerAutocompletionTransformer
: Needs to implementAutocompleteTransformer
ExtensionsBuilder#registerCommandDependency
: Replaced with standard dependency injectionExtensionsBuilder#registerConstructorParameter
: Replaced with standard dependency injectionExtensionsBuilder#registerCustomResolver
: Needs to implementClassParameterResolver
andICustomResolver
ExtensionsBuilder#registerDynamicInstanceSupplier
: Needs to implementDynamicSupplier
ExtensionsBuilder#registerInstanceSupplier
: Replaced by service factoriesExtensionsBuilder#registerParameterResolver
: Needs to implementClassParameterResolver
and the resolver interface of your choices
Service properties¶
Warning
This section only applies to the built-in dependency injection
Service providers can have names, additional registered types, and an instantiation priority.
Service names¶
Named services may be useful if you have multiple services of the same type, but need to get a specific one.
The name is either defined by using @ServiceName
, or with BService#name
on the service provider.
Example
You can have a caching HttpClient
named cachingHttpClient
, while the usual client uses the default name.
Service types¶
In addition to the type of the service provider,
@ServiceType
enables you to register a service as a supertype.
Service priority¶
Service priorities control how service providers are sorted.
A higher priority means that the service will be loaded first, or that an interfaced service will appear first when requesting interfaced services.
The priority is either defined by using @ServicePriority
, or with BService#priority
on the service provider,
see their documentation to learn what how service providers are sorted.