Dependency injection

Friday, February 5, 2010
I've written a unit test that demonstrates a very rudimentary form of dependency injection in Tart. Here's a small code snippet:

import tart.inject.Inject;
import tart.inject.Injector;

// A class that has a no-arg constructor.
class NoArgConstructor {
  def construct {}

// A class that has a single arg constructor annotated with @Inject.
class SingleArgConstructor {
  @Inject def construct(a0:NoArgConstructor) {}

def testInjection() {
  // Request an instance of type 'SingleArgConstructor'.
  let instance = Injector().getInstance(SingleArgConstructor);

In the above example, the call to 'getInstance' causes two objects to be created. First, it creates an instance of 'NoArgConstructor', because that is needed as a constructor parameter to the second instance created which is of type 'SingleArgConstructor'.

This example does not yet support any of the Guice-style features such as interface bindings, singletons, and so on. But those could easily be added.

Under the hood, what is happening is this: When the injector's 'getInstance' method is called, it uses reflection to examine the type literal passed to it. It searches the resulting class definition for a constructor annotated with @Inject, or failing that, a constructor with no arguments. It then examines the list of constructor parameter types, and for each one it recursively calls getInstance() for that type. These constructor parameters are then stored in an array, and are then used to invoke the constructor method via reflection.

This is also the first time that Tart has supported runtime attributes (the @Inject annotation), up to this point all attributes have not been retained past compile time. (Only annotations who are specially marked are retained in the output binary.)

I plan to model my dependency injection framework after Guice, up to a point. One deviation is that in a language in which function closures are a first-class type, there's no need for a Provider type, since the injector can simply return a factory function instead. So for example, if you want to create an object lazily, you will eventually be able to say:

let provider = Injector().getProvider(SingleArgConstructor);

// later...
let instance = provider();

The call to 'getProvider' will return a function closure that will construct a new instance of the requested type each time it is called. (Unless the type is marked as a singleton, in which case it will return the same instance each time.)


Post a Comment