< Back to all posts

Quarkus Dependency Injection

Quarkus ArC is a build-time oriented dependency injection based on CDI 2.0. In this blogpost, we’re going to explain the relationship to the specification and describe some of the benefits and drawbacks of the build-time processing design.

Compatibility

It does not make sense to reinvent the wheel, especially when it comes to dependency injection. There are many frameworks that try to solve similar problems. A year ago, we made a design decision to build Quarkus DI on top of CDI. We had some very good reasons to choose CDI:

  1. CDI is a mature and proven component model

  2. We at Red Hat have almost ten years of experience with developing Weld - the CDI Reference Implementation

  3. The CDI API is built on top of javax.inject so that it should be easy to migrate from any DI framework compatible with @Inject

Our primary goal was to implement a supersonic build-time oriented DI solution compatible with CDI. This would allow users to continue using CDI in their applications but also leverage Quarkus build-time optimizations. However, ArC is not a full CDI implementation verified by the TCK - see also the list of supported features and the list of limitations.

Build-time Processing Benefits and Drawbacks

Fail Fast

Beans and dependencies are validated during the build so that your application can never fail in production with common problems such as AmbiguousResolutionException or UnsatisfiedResolutionException.

Instant Startup

When the app starts ArC just loads all the metadata and initializes some internal structures. There is no need to analyze the application classes again. This means negligible startup overhead.

This applies to both the GraalVM and OpenJDK HotSpot runtimes.

Minimized Runtime

In Quarkus 0.19, ArC plus integration runtime consist of 72 classes and occupies ~ 140 KB in jars. Weld 3.1.1 (CDI Reference Implementation) core is roughly 1200 classes and approx. 2 MB jar. In other words, ArC runtime takes approx. 7% of the Weld runtime in terms of number of classes and jar footprint.

Optimized Code Paths and Metadata Structures

When generating the metadata classes ArC can sometimes produce leaner and smarter logic because it has the whole application analyzed already. This is one of the areas where we would like to develop and improve ArC.

Extension Points

Unfortunately, CDI portable extensions are inherently runtime constructs and therefore cannot be fully supported in Quarkus. In fact, all CDI extensions are ignored at the moment. Nevertheless, most of the functionality can be achieved using Quarkus extensions. The CDI extensions are encouraged to generalize the code, and if possible provide a Quarkus extension to make full use of build-time metadata processing.

Non-standard Features

ArC is not limited to standards and we’re constantly looking for ways to go beyond and extend the possibilities. Following are some examples of non-standard features provided by Quarkus DI.

Qualified Injected Fields

Normally, if you declare an injected field you’ll always need to use @Inject and optionally required qualifiers:

  @Inject
  @ConfigProperty(name = "cool")
  String coolProperty;

In Quarkus, you can skip the @Inject annotation completely if an injected field declares at least one qualifier:

  @ConfigProperty(name = "cool")
  String coolProperty;
@Inject is still required for constructor and method injection.

Simplified Constructor Injection

In CDI, a normal scoped bean must always declare a no-args constructor (this constructor is normally generated by the compiler unless you declare any other constructor). However, this requirement complicates constructor injection - you would need to provide a dummy no-args constructor to make things work in CDI.

@ApplicationScoped
public class MyCoolService {

  private SimpleProcessor processor;

  MyCoolService() { // dummy constructor needed
  }

  @Inject // constructor injection
  MyCoolService(SimpleProcessor processor) {
    this.processor = processor;
  }
}

There is no need to declare dummy constructors for normal scoped bean in Quarkus - they are generated automatically. Also if there’s only one constructor there is no need for @Inject.

@ApplicationScoped
public class MyCoolService {

  private SimpleProcessor processor;

  MyCoolService(SimpleProcessor processor) {
    this.processor = processor;
  }
}
We don’t generate a no-args constructor automatically if a bean class extends another class that does not declare a no-args constructor.

Default Beans

CDI has a feature called alternatives. Simply said a bean annotated with @Alternative and @Priority takes precedence over the non-alternative beans during type-safe resolution.

class MyBean {
}

@Alternative
@Priority(1)
class MyAlternativeBean extends MyBean {
}

@Inject // MyAlternativeBean wins and is injected!
MyBean bean;

So, what if a user wants to override a bean defined by a library/extension? The bean would have to be marked as a CDI @Alternative that is enabled using the @Priority annotation. Is there a simpler approach? Yes, there is. You can use a non-standard feature called "default beans". In this case, a bean that can be overridden should be annotated with @io.quarkus.arc.DefaultBean. And that’s it.

@DefaultBean
class MyBean {
}

class MyOwnBean extends MyBean {
}

@Inject // MyOwnBean wins and is injected!
MyBean bean;

Removing Unused Beans

GraalVM native image does a pretty good job when removing all classes that are not reachable from your application. However, sometimes checking reachability is not enough. Sometimes the framework itself must decide whether a component is needed or not. In standard CDI, all beans are retained by the container no matter whether they’re needed or not.

Let’s suppose we have a bean class org.acme.Foo. This bean class imports and uses a lot of various classes. It’s annotated with @ApplicationScoped and so Quarkus needs to generate a bean metadata class and a client proxy and register this metadata when the application starts. But what if nobody ever uses this bean? We would still hold a reference on the generated metadata, and the bean class itself, and its dependencies. In other words, all these classes would be reachable.

Quarkus attempts to remove all unused beans during build by default. This helps to reduce the number of generated classes and also the amount of memory needed at runtime. But how do we actually detect an unused bean? The rules are described in the reference guide but simply said: if a bean is not injected anywhere and cannot be reached in any other standard way (e.g. observer notification) it is removed. Moreover, users can instruct the container to not remove a bean by annotating the bean class with @io.quarkus.arc.Unremovable. Finally, this optimization can be disabled and fine tuned by using the quarkus.arc.remove-unused-beans property.

This feature applies to the JVM mode as well.