ActiveInject. Fast and Lightweight Dependency Injection Library
valerialistratova
Posted on October 19, 2020
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());
}
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); }
};
}
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);
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 {
}
Next, instantiate your scope:
public static final Scope ORDER_SCOPE = Scope.of(OrderScope.class);
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();}
};
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.
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.
Posted on October 19, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.