Programming · Quarkus

Quarkus Guide: Contexts and Dependency Injection

Here I’ll cover the basics of CDI (contexts and dependency injection) on Quarkus.

This will include all jargon that comes with it (bean, injection point, …).

If you’re new to Java EE, you’re in the right place!

  1. Simple demo
  2. What is a bean? And an Injection Point?
    1. Producing a bean
    2. Producing and injecting different variants of a bean
  3. Injecting multiple beans / Ambiguous dependency exception
    1. Using multiple beans of the same type
  4. Scopes
  5. Client proxy
  6. @Singleton or @ApplicationScoped?
  7. When would someone need @Singleton?

Simple demo

To start off, let’s create a simple example, to get some practice in.

Head over to https://code.quarkus.io, to set up the project.

We just need the RESTEasy Reactive extension.

Don’t enable “Starter Code”, that’s cheating!

Note: Java will be used, Kotlin is currently still in preview, but if you want to use it, check this before.

Now generate and download it, and we’re ready to inject some beans.

Create two classes:

  • PersonController
  • PersonService

For the PersonController, we’ll begin with this:

package me.davidgomes.blog.quarkus;

import javax.ws.rs.Path;

@Path("/people")
public class PersonController {
    
}

If you’re on IntelliJ, you’ll notice you get a warning, it will be tackled in a second.

The @Path annotation makes it a REST endpoint, on /people.

And for the service:

package me.davidgomes.blog.quarkus;

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class PersonService {
    
}

Here we start using scopes, in this case, the @ApplicationScoped annotation means only one instance will be created for our application. (Don’t worry, all scopes will be explained later in this post)

This is pretty barebones, let’s add some functionality.

// PersonService.java

String sayHello(String name) {
    return "Hello " + name + "!";
}

Now we want to call that from the Controller, and this is where injection begins.

// PersonController.java

final PersonService service;

PersonController(PersonService service) {
    this.service = service;
}

We inject it by the constructor and, unlike other frameworks, you don’t need to annotate with @Inject. (in case you’ve seen that elsewhere)

@GET
public String hello(@QueryParam("name") String name) {
    return service.sayHello(name);
}

Here we expose the HTTP GET verb/method, on our previously declared /people endpoint, with the name as a query parameter.

And the endpoint returns:

$ curl http://localhost:8080/people?name=David
Hello David!

What is a bean? And an Injection Point?

A bean refers to the class you’re injecting/producing, and an injection point is where you’re getting it.

In our example above, the bean is PersonService and the injection point is the service variable in the controller.

This terminology helps when reading the official docs.

Producing a bean

You can inject a bean, as long as it has a scope or is produced somewhere.

To produce a bean, you create a function with @Produces.

import javax.enterprise.inject.Produces;

public class EarthProducer {
    @Produces
    Earth earth() {
        return new Earth(2022);
    }
}

Here Earth is simply a POJO.

Although we don’t specify its scope, by default it has the ApplicationScoped as well.

If we specify the scope, @Produces can be omitted.

@ApplicationScoped
Earth earth() {
    return new Earth(2022);
}

You can try injecting via the constructor, as we’ve done before.

You can also add parameters for beans you want to be injected, just as we’ve done in the constructor of the service.

Producing and injecting different variants of a bean

Imagine you have a service, with a configuration-based variance. You want both to be injected into different parts of the code.

To re-use this base service:

public abstract class HelloService {

    final String name;

    HelloService(String name) {
        this.name = name;
    }

    String sayHello() {
        return "Hi " + name + "!";
    }
}

You can make use of @Produces and @Named, which is a qualifier for the bean you’re producing.

import javax.enterprise.inject.Produces;

public class HelloServiceProducer {

    @Named("earth")
    @Produces
    HelloService earthHelloService() {
        return new HelloService("Mother Nature");
    }

    @Named("person")
    @Produces
    HelloService personHelloService() {
        return new HelloService("Ordinary person");
    }
}

Now in the PersonController, if you wish to use its HelloService variant, you need to specify that same qualifier.

@Path("/people")
public class PersonController {

    final HelloService service;

    // Note: annotate only here in the parameter, since the service is being injected here
    PersonController(@Named("person") HelloService service) {
        this.service = service;
    }

    @GET
    public String hello() {
        return service.sayHello();
    }
}

As you can see, I haven’t annotated the HelloService itself with any scope, that’s because you annotate the implementations themselves, not the abstractions (interface/base class), since these cannot be instantiated.

Injecting multiple beans / Ambiguous dependency exception

If you have 2 beans that match what you’re asking in your injection point without any qualifier (like the @Named) you’re going to get an exception.

Consider this:

public interface HelloService {

    String sayHello();
}

// Person bean

@ApplicationScoped
public class PersonHelloService implements HelloService {

    @Override
    public String sayHello() {
        return "Hi, I'm a person!";
    }
}

// Earth bean

@ApplicationScoped
public class EarthHelloService implements HelloService {

    @Override
    public String sayHello() {
        return "Hello, I'm mother nature!";
    }
}

There are no qualifiers, so if we have our injection point as such:

final HelloService service;

PersonController(HelloService service) {
    this.service = service;
}

We get “javax.enterprise.inject.AmbiguousResolutionException: Ambiguous dependencies […]”, at compile-time. (that’s the beauty of Quarkus, erroring in compile-time so we don’t get surprised in runtime!)

It’s not that it’s bad to have multiple implementations, we just need to properly work with them, in this case, we could have used the @Named qualifier as done in the previous section.

@ApplicationScoped
@Named("person")
public class PersonHelloService implements HelloService {
...
}

// Controller

final HelloService service;

PersonController(@Named("person") HelloService service) {
    this.service = service;
}

Remember, annotate in the constructor, since that’s where we want it injected.

Keep in mind that, if you inject without this @Named("person"), you’re still going to get the exception, because you need to add a qualifier to the EarthService too.

Using multiple beans of the same type

However, we could want to call multiple implementations.
For that, you must rely on Instance<YourBean>, which is the programmatic way of injecting beans.

// list of Instances
final Instance<HelloService> services;

PersonController(Instance<HelloService> services) {
    this.services = services;
}

...
var hellos = new StringBuilder();

for (var service : services) {
    hellos.append(service.sayHello()).append("\n");
}

return hellos.toString();

// Output:
// Hi, I'm a person!
// Hello, I'm mother nature!

Mostly for curiosity, since I’ve never had such a need.
Perhaps you could just make use of the first in it, as a dirty hack for the ambiguous problem. ¯\_(ツ)_/¯

Scopes

These are the scopes available:

ApplicationScopedOne instance for the entire app
RequestScopedOne instance per request
SessionScopedOne instance per HTTP session
* SingletonOne instance for the entire app
* DependentUses the same scope of the bean injecting it
* Pseudo-scope: a term to imply that they aren’t exactly a “scope” as the others.

The scopes are instantiated lazily, only after calling a method upon it. (we’ll see how this is achieved in the next section 😉)

While the pseudo ones are instantiated right after being injected.

Client proxy

The scopes all have a “client proxy”, which is a class that wraps the bean.

It is this client proxy that allows for lazy instantiation at the call level.

When you inject a scoped bean, it doesn’t actually inject the bean itself, but the proxy.

It is only when you call something on the bean (which is actually the proxy), that it does initialize the bean. (in case it isn’t initialized yet)

They also allow for another thing: circular dependency.

A circular dependency is when you have a PersonService calling HelloService, and PersonService also calling HelloService.

Although you shouldn’t rely on that, since it does have some drawbacks.

@Singleton or @ApplicationScoped?

At first sight, you’d consider @Singleton, by the fact that it doesn’t have the seriooooously small overhead, of not calling through the client proxy.

However, there are 2 drawbacks. By using @Singleton you:

  • Can’t mock in a test
    • although one can argue that you needn’t injection in a test
      • in the unit ones you use the classes directly
      • in integration, you preferably don’t use mocks
  • Can’t have the bean destroyed and recreated in runtime
    • but… who has ever used that?

And, in the Quarkus article, they mention that on an @ApplicationScoped, you shouldn’t directly use fields, because the proxy only delegates/proxies the methods, but it’s also something uncommon, people always use setters and getters anyway.

In the end, I don’t see it being a game-changer, but Quarkus does recommend using @ApplicationScoped unless there is a good reason to go for @Singleton.

When would someone need @Singleton?

In a project of mine, I had to use @Singleton to produce instances of the KMongo library, because, at the time of writing, their classes are final. (it’s the default on Kotlin, although they could have made them open)

Summary

In this post, we went through the basics of dependency injection in Quarkus, including its terminology.

Going over how to inject a service, produce beans, and how the different scopes differ.

Now it’s time to start using Quarkus in your projects!

Leave a comment