Skip to content

Retrieving services

Any class given by a service provider can be injected into other service providers, requesting a service is as simple as declaring a parameter in the class's constructor, or the service factory's parameters.

Named services can be retrieved by using @ServiceName on the parameter, this can be omitted if the parameter name matches a service with a compatible type.

Tip

You can also get services manually with BContext or ServiceContainer, the latter has all methods available, including Kotlin extensions.

Example

@BService // Enables the service to request services and be requested
public class TagDatabase { /* */ }
@Command // Enables the command to request services and be requested
public class TagCommand {
    private final Component components;
    private final TagDatabase tagDatabase;

    public TagCommand(
        // You can even request framework services, as long as they are annotated with @BService or @InterfacedService
        Component components,
        // and your own services
        TagDatabase tagDatabase
    ) {
        this.components = components;
        this.tagDatabase = tagDatabase;
    }

    /* */
}
@BService // Enables the service to request services and be requested
class TagDatabase { /* */ }
@Command // Enables the command to request services and be requested
class TagCommand(
    // You can even request framework services, as long as they are annotated with @BService or @InterfacedService
    // Here I've named it "componentsService" because "components" might conflict with some JDA-KTX builders
    private val componentsService: Components,
    // and your own services
    private val tagDatabase: TagDatabase
) {
    /* */
}
Retrieving services by name

Consider the following service providers:

@BService
class HttpClientProvider {
//    @Primary // This is only needed if you try to get an OkHttpClient without matching the name
    @BService
    fun httpClient(): OkHttpClient = OkHttpClient()

    @BService
    fun cachedHttpClient(
        // Inject the default http client (declared above)
        // This is not the same as calling the method! as it would create 2 different clients

        // This would not work if the parameter was named differently,
        // unless @Primary was used on the default declaration above
        httpClient: OkHttpClient
    ): OkHttpClient {
        val tempDirectory = Files.createTempDirectory(null).toFile()
        return httpClient.newBuilder()
            .cache(Cache(tempDirectory, maxSize = 1024 * 1024))
            .build()
    }
}
@BService
class MyApi(@ServiceName("cachedHttpClient") httpClient: HttpClient)
@BService
class MyApi(private val cachedHttpClient: HttpClient)
@BService
public class HttpClientProvider {
//    @Primary // This is only needed if you try to get an OkHttpClient without matching the name
    @BService
    public OkHttpClient httpClient() {
        return new OkHttpClient();
    }

    @BService
    public OkHttpClient cachedHttpClient(
            // Inject the default http client (declared above)
            // This is not the same as calling the method! as it would create 2 different clients

            // This would not work if the parameter was named differently,
            // unless @Primary was used on the default declaration above
            OkHttpClient httpClient
    ) throws IOException {
        final File tempDirectory = Files.createTempDirectory(null).toFile();
        return httpClient.newBuilder()
                .cache(new Cache(tempDirectory, 1024 * 1024))
                .build();
    }
}
@BService
public class MyApi {
    public MyApi(@ServiceName("cachedHttpClient") HttpClient httpClient) {
        // ...
    }
}
@BService
public class MyApi {
    public MyApi(HttpClient cachedHttpClient) {
        // ...
    }
}

Warning

For this to work, you need to enable Java parameter names

Primary providers

When requesting a service of a specific type/name, there must be at most one usable service provider.

For example, if you have two service factories with the same return type:

  • ❌ If both are usable
  • ✅ One has a failing condition, meaning you have one usable provider
  • ✅ One is annotated with @Primary, in which case this one is prioritized

Note

You can still retrieve existing services with ServiceContainer#getInterfacedServices/getInterfacedServiceTypes

Interfaced services

A list which the element type is an interfaced service can be requested, the list will then contain all instantiable instances with the specified type.

Example

List<ApplicationCommandFilter<?>> will contain all instances implementing ApplicationCommandFilter, which are usable.

Lazy services

Lazy service retrieval enables you to get lazily-created service, delaying the initialization, or to get services that are not yet available, such as manually injected services (like JDA).

Retrieving a lazy service

Request a Lazy with the element type being the requested service, and then get the service when needed by using Lazy#getValue.

Request a ServiceContainer and use a delegated property, such as:

private val helpCommand: IHelpCommand by serviceContainer.lazy()

Note

Lazy injections cannot contain a list of interfaced services, nor can a list of lazy services be requested.

Optional services

When a requested service is not available, and is not a soft-dependency, service creation will fail.

In case your service does not always require the service, you can prevent failure by using Kotlin's nullable / optional parameters, but Java users will need a runtime-retained @Nullable annotation (such as @javax.annotation.Nullable, or, in checker-framework or JSpecify) or @Optional.

Lazy nullability

Lazy services can also have their element type be marked as nullable, for example, Lazy<@Nullable IHelpCommand>.