DEV Community

Cover image for ActiveInject. Fast and Lightweight Dependency Injection Library
valerialistratova
valerialistratova

Posted on • Edited on

ActiveInject. Fast and Lightweight Dependency Injection Library

What is ActiveInject?

ActiveInject is a lightning-fast and powerful dependency injection library. It has a lot of tools and features to offer: support for nested scopes, singletons and transient bindings, modules, multi-threaded and single-threaded injectors.

At the same time it’s thoroughly optimized with all the dependencies graph preprocessing performed at startup time. According to the benchmarks, in some scenarios ActiveInject is 5.5 times faster than Guice and hundreds of times faster than Spring DI. You can check the benchmark sources here.

ActiveInject is an independent technology of ActiveJ platform. It has no third-party dependencies on its own and can be used as a stand-alone DI library.

Getting started

Let’s try the library out and bake some virtual cookies using ActiveInject. A cookie requires the following ingredients: Flour, Sugar and Butter. These ingredients form a Pastry which can be baked into a Cookie. Assume each of these entities has a POJO. Let’s start with a basic example:

public void provideAnnotation() {
  Module cookbook = new AbstractModule() {
     @Provides
     Sugar sugar() { return new Sugar("WhiteSugar", 10.f); }

     @Provides
     Butter butter() { return new Butter("PerfectButter", 20.0f); }

     @Provides
     Flour flour() { return new Flour("GoodFlour", 100.0f); }

     @Provides
     Pastry pastry(Sugar sugar, Butter butter, Flour flour) {
        return new Pastry(sugar, butter, flour);
     }

     @Provides
     Cookie cookie(Pastry pastry) {
        return new Cookie(pastry);
     }
  };

  Injector injector = Injector.of(cookbook);
  injector.getInstance(Cookie.class).getPastry().getButter().getName());
}
Enter fullscreen mode Exit fullscreen mode

Here we’ve created an AbstractModule named cookbook that contains all the required bindings, or “recipes”, for the ingredients. Call Injector.getInstance method to get an instance of the Cookie.

How does it work from the inside? Injector provides all the required dependencies for the component recursively traversing the dependencies graph in a postorder way. So it first created Sugar, Butter and Flour, the next was Pastry, and finally a Cookie.

Named bindings

But what if you need some special cookie recipes for different people? For example, if you need a sugar-free Cookie in addition to a regular one. In this case, you can use @Named annotation:

public void namedAnnotationSnippet() {
  Module cookbook = new AbstractModule() {
     @Provides
     @Named("zerosugar")
     Sugar sugar() { return new Sugar("SugarFree", 0.f); }

     @Provides
     @Named("normal")
     Sugar sugar2() { return new Sugar("WhiteSugar", 10.f); }

     @Provides
     Butter butter() { return new Butter("PerfectButter", 20.f); }

     @Provides
     Flour flour() { return new Flour("GoodFlour", 100.f); }

     @Provides
     @Named("normal")
     Pastry pastry1(@Named("normal") Sugar sugar, Butter butter, Flour flour) {
        return new Pastry(sugar, butter, flour);
     }

     @Provides
     @Named("zerosugar")
     Pastry pastry2(@Named("zerosugar") Sugar sugar, Butter butter, Flour flour) {
        return new Pastry(sugar, butter, flour);
     }

     @Provides
     @Named("normal")
     Cookie cookie1(@Named("normal") Pastry pastry) {
        return new Cookie(pastry);
     }

     @Provides
     @Named("zerosugar")
     Cookie cookie2(@Named("zerosugar") Pastry pastry) { return new Cookie(pastry); }
  };
}
Enter fullscreen mode Exit fullscreen mode

The usage of the @Named annotation is pretty self-illustrative. After annotating bindings you can call either injector.getInstance(Key.ofName(Cookie.class, "normal")) or injector.getInstance(Key.ofName(Cookie.class, "zerosugar")) and get different instances of the Cookie class.

Non-singleton instances

One of the important ActiveInject features is that all the created instances are singleton by default. If you call injector.getInstance(Cookie.class) several times, each time you’ll get the very same instance from cache. You can easily check it with the following code:

AbstractModule cookbook = new AbstractModule() {
    @Provides
    Integer giveMe() {
        return random.nextInt(1000);
    }
};
Injector injector = Injector.of(cookbook);
Integer firstInt = injector.getInstance(Integer.class);
Integer secondInt = injector.getInstance(Integer.class);
System.out.println("First : " + firstInt + ", second  : " + secondInt);
Enter fullscreen mode Exit fullscreen mode

This is a useful optimization, but what if we need non-singleton cookies? In this case, you can use scopes.

Scope creates “local singletons” which live as long as the scope itself. ActiveInject scopes are a bit different from other DI libraries. The internal structure of the Injector is a prefix tree and the prefix is a scope. If you create an Injector that is set to a particular scope, it means that Injector enters this scope. This can be done several times, so that there are multiple injectors in a single scope.

Let’s create a scope for our cookies. The identifiers of the tree are annotations. So you can simply create your custom scope annotations:

@ScopeAnnotation(threadsafe = false)
@Target({ElementType.METHOD})
@Retention(RUNTIME)
public @interface OrderScope {
}
Enter fullscreen mode Exit fullscreen mode

Next, instantiate your scope:

public static final Scope ORDER_SCOPE = Scope.of(OrderScope.class);
Enter fullscreen mode Exit fullscreen mode

Add @OrderScope annotation to the instances that are needed as non-singletons (Cookie, Pastry, Sugar, Flour, Butter) in the following way:

Module cookbook = new AbstractModule() {
  @Provides
 @OrderScope
  Sugar sugar() { return new Sugar("WhiteSugar", 10.f); }



  @Provides
  Kitchen kitchen() {return new Kitchen();}
};
Enter fullscreen mode Exit fullscreen mode

Now each time we need a new instance of Сookie, our Injector will create a subinjector to enter the order scope and create a new instance of Cookie, while the Kitchen will always stay singleton in the root scope.
ActiveInject scopes

Using ActiveInject with ActiveSpecializer

One more interesting ActiveInject feature is that it is fully compatible with another ActiveJ library named ActiveSpecializer.

ActiveSpecializer is a unique technology that relies on a ground-breaking concept of using runtime information about instances of the classes. ActiveSpecializer rewrites your code directly in runtime, using runtime information that is contained in the instances of your classes. To be more precise, it transforms all class fields into static class fields, de-virtualizes all the virtual methods calls and replaces them with static method calls. ActiveSpecializer shows its best with AST-like structures, so it is very efficient with ActiveInject. According to the benchmarks, ActiveSpecializer can speed up your code to up to 7 times in some use cases.

To apply ActiveSpecializer to your Injector simply call Injector.useSpecializer method before Injector instantiation.

Summary

ActiveInject goes far beyond these basic examples and is capable of fine tuning to match all your needs. You can find more examples on the official ActiveInject website.

Since ActiveInject is a part of ActiveJ, it is perfectly compatible with all the platform’s components: HTTP servers and servlets, RPC implementation, bytecode manipulation tools, abstractions for efficient management of distributed file storage and others.

Top comments (0)