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
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
) {
    /* */
}
@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;
    }

    /* */
}
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 ServiceContainer and use a delegated property, such as:

private val helpCommand: IHelpCommand by serviceContainer.lazy()

Request a LazyService with the element type being the requested service, and then get the service when needed by using getValue().

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.

Optional lazy services

When you are about to request the service from LazyService, you can use canCreateService or getServiceError to check if it is available at this point in time.