Skip to main content

ActiveJ Inject under the hood

Implicit bindings

ActiveJ Inject provides several bindings implicitly. Some of these bindings are required for internal use while still can be used by application code.

These bindings are:

  • InstanceInjector binding used for injecting fields
  • InstanceProvider binding used for providing instances similar to Injector#getInstance method
  • OptionalDependency binding that can be used to determine whether a binding is present or not
  • A Key<T> binding that you can use to receive an ActiveJ Inject Key for a given type

Instance Injector

InstanceInjector can inject instances into @Inject fields and methods of some already existing objects. Consider this simple example:

@InjectString message;
@ProvidesString message() {  return "Hello, world!";}
@Overrideprotected void run() {  System.out.println(message);}
public static void main(String[] args) throws Exception {  Launcher launcher = new InstanceInjectorExample();  launcher.launch(args);}

The question that might bother you - how does the Launcher actually know that the message variable contains "Hello, world!" string, to display it in the run() method?

Here during the internal work of DI, the InstanceInjector in fact gives launcher a hand:

private void postInjectInstances(String[] args) {  Injector injector = this.createInjector(args);  InstanceInjector<Launcher> instanceInjector = injector.getInstanceInjector(Launcher.class);  instanceInjector.injectInto(this);}
  • createInjector produces injector with the given arguments.
  • instanceInjector gets all the required data from the injector.
  • injectInto injects the data into our empty instances.

You can find example sources on GitHub

Instance Provider

InstanceProvider is a version of `Injector.getInstance()` with a baked-in key. It can be fluently requested by provider methods.

In the AbstractModule we explicitly add InstanceProvider binding for Integer using bindInstanceProvider helper method and provide Integer factory function:

AbstractModule cookbook = new AbstractModule() {  @Override  protected void configure() {    bindInstanceProvider(Integer.class);  }
  @Provides  Integer giveMe() {    return random.nextInt(1000);  }};

After creating an Injector of the cookbook, we get instance of the Key<InstanceProvider<Integer>>. Now simply use provider.get() to get a lazy Integer instance.

Injector injector = Injector.of(cookbook);InstanceProvider<Integer> provider = injector.getInstance(new Key<InstanceProvider<Integer>>() {});// lazy value get.Integer someInt = provider.get();System.out.println(someInt);

Unlike the previous example, If you call provide.get() several times, you'll receive the same value.

You can find example sources on GitHub

Binding Generators

Let's consider a Cookbook example. This time we have the same POJO ingredients, but now our cookie is a generic Cookie<T> and has a field Optional<T> pastry:

static class Cookie<T> {  private final Optional<T> pastry;
  @Inject  Cookie(Optional<T> pastry) {    this.pastry = pastry;  }
  public Optional<T> getPastry() {    return pastry;  }}

Next, we create an AbstractModule cookbook and override its configure() method:

AbstractModule cookbook = new AbstractModule() {  @Override  protected void configure() {    // note (1)    generate(Optional.class, (bindings, scope, key) -> {      Binding<Object> binding = bindings.get(key.getTypeParameter(0));      return binding != null ?          binding.mapInstance(Optional::of) :          Binding.toInstance(Optional.empty());    });
    bind(new Key<Cookie<Pastry>>() {});  }

generate() adds a BindingGenerator for a given class to this module, in this case it is an Optional. BindingGenerator tries to generate a missing dependency binding when Injector compiles the final binding graph trie. You can substitute generate() with the following code:

@Provides<T> Optional<T> pastry(@io.activej.di.annotation.Optional T instance) {    return Optional.ofNullable(instance);

Now you can create cookbook injector and get an instance of Cookie<Pastry>:

Injector injector = Injector.of(cookbook);System.out.println(injector.getInstance(new Key<Cookie<Pastry>>() {}).getPastry().orElseThrow(AssertionError::new).getButter().getName());

You can find example sources on GitHub

More information on generators can be found here.

Inspecting created dependency graph

ActiveJ Inject provides efficient DSL for inspecting created instances, scopes and dependency graph visualization. In this Cookie example we, as usual, create Sugar, Butter, Flour, Pastry and Cookie POJOs, cookbook AbstractModule with two scopes (parent scope for Cookie and @OrderScope for ingredients) and cookbook injector.

First, let's overview three Injector methods: peekInstance, hasInstance and getInstance. They allow to inspect created instances:

Cookie cookie1 = injector.peekInstance(Cookie.class);System.out.println("Instance is present in injector before 'get' : " + injector.hasInstance(Cookie.class));System.out.println("Instance before get : " + cookie1);
Cookie cookie = injector.getInstance(Cookie.class);
Cookie cookie2 = injector.peekInstance(Cookie.class);System.out.println("Instance is present in injector after 'get' : " + injector.hasInstance(Cookie.class));System.out.println("Instance after get : " + cookie2);System.out.println();    /// created instance check.System.out.println("Instances are same : " + cookie.equals(cookie2));
  • peekInstance - returns an instance only if it was already created by getInstance call before
  • hasInstance - checks if an instance of the provided key was created by getInstance call before
  • getInstance - returns an instance of the provided key

Next, we'll explore tools for scopes inspecting:

final Scope ORDER_SCOPE = Scope.of(OrderScope.class);
System.out.println("Parent injector, before entering scope : " + injector);
Injector subInjector = injector.enterScope(ORDER_SCOPE);System.out.println("Parent injector, after entering scope : " + subInjector.getParent());System.out.println("Parent injector is 'injector' : " + injector.equals(subInjector.getParent()));
System.out.println("Pastry binding check : " + subInjector.getBinding(Pastry.class));
  • getParent - returns parent scope of the current scope
  • getBinding - returns dependencies of provided binding
  • getBindings - returns dependencies of the provided scope (including Injector)
Utils.printGraphVizGraph(subInjector.getBindingsTrie());

You'll receive the following output:

digraph {    rankdir=BT;    "()->DiDependencyGraphExplore$Flour" [label="DiDependencyGraphExplore$Flour"];    "()->DiDependencyGraphExplore$Sugar" [label="DiDependencyGraphExplore$Sugar"];    "()->DiDependencyGraphExplore$Butter" [label="DiDependencyGraphExplore$Butter"];    "()->DiDependencyGraphExplore$Cookie" [label="DiDependencyGraphExplore$Cookie"];    "()->io.activej.di.core.Injector" [label="Injector"];    "()->DiDependencyGraphExplore$Pastry" [label="DiDependencyGraphExplore$Pastry"];
    { rank=same; "()->DiDependencyGraphExplore$Flour" "()->DiDependencyGraphExplore$Sugar" "()->DiDependencyGraphExplore$Butter" "()->io.activej.di.core.Injector" }
    "()->DiDependencyGraphExplore$Cookie" -> "()->DiDependencyGraphExplore$Pastry";    "()->DiDependencyGraphExplore$Pastry" -> "()->DiDependencyGraphExplore$Butter";    "()->DiDependencyGraphExplore$Pastry" -> "()->DiDependencyGraphExplore$Flour";    "()->DiDependencyGraphExplore$Pastry" -> "()->DiDependencyGraphExplore$Sugar";}

Which can be transformed into the following graph:

graph BT DiDependencyGraphExplore$Cookie --> id1(DiDependencyGraphExplore$Pastry) id1 --> DiDependencyGraphExplore$Flour id1 --> DiDependencyGraphExplore$Sugar id1 --> DiDependencyGraphExplore$Butter Injector

You can find example sources on GitHub