Dependency Injectionn Containers

This article gives the background to the adoption of a container system in VODesktop, and the selection of Hivemind in particular.

Assembling Programs

Programs in Java are assembled out of objects.

Objects are instantiations of classes.

Well-designed objects depend on other objects for some of their functionality - they're composed from them, or aggregate other objects together.

These dependent objects are either created one-per parent, or are shared or (a singleton) between all instances. There are other possible patterns too - groups (such as singleto-per-window), pools (such as thread pools).

The core question is - 'how best to assemble the objects into an application?'

Vanilla Java

One technique, used in small Java programs, is to have the parent / owner object create all the dependent objects it requires. This makes the code brittle - as the implementation itself specifies the implementation class to use for dependent objects. This technique gives no easy way to access the 'shared' objects - leading to poor architectures that use static classes and static singletons.

class FooImpl {
   private final Bar b;
   private final Neep n;

   public FooImpl() {
      // initialize dependent objects
      this.b = new BarImpl(); // implementation class is fixed.
      this.n = Neep.getInstance(); // shared object plucked from
environment
     // perform further initialization
   }
   ...
  // rest of class.
}

An object that creates all its dependent objects and accesses finds its own shared objects (whether this is via global singletons,context objects, or JNDI lookup) is opaque, and because of this not easily tested, reused or extended. It's strongly tied into the environment, so it's hard to substitute objects for other implementations (for example test mocks)

This is not a good way to go.

Dependency Injection

A better pattern is called dependency injection In this pattern, some external agent takes care of instantiating objects and providing their dependencies.

As well as solving the problems identified with the 'Vanilla Java' technique, dependency injection also has the benefit that classes only need to describe what they do - not how to construct themselves. This simplifies the code by separating concerns - the construction task is left to an external agent.

A sensible use of Interface-Implementation separation adds great flexibility - allowing the external agent to substitute different implementations. This allows mocks for testing, or implementations for different settings to be supplied without the consumer of the object caring.

Constructor Injection

There's 2 main forms of Dependency Injection - Constructor Injection and Setter Injection .

Constructor injection is when the external agent supplies dependent objects to the classes' constructor . Setter Injection is when dependent objects are supplied via the classes' setter methods .

I prefer constructor injection, as the constructor clearly declares what dependent objects are required to construct an instance. Even better, the instance is constructed in a valid state. This is less clear with setter injection - which set() methods are to be called? and which also often requires a separate init() method to be defined, which the external agent is to call after performing the setter injection to 'start' the instance.

Most of the code in VODesktop are designed around this constructor injection pattern. The typical pattern is something like:

class FooImpl implements Foo {
   // dependent objects, declared as 'final'
   private final Bar b;
   private final Neep n;

   public FooImpl(Bar b,Neep n) {
      // initialize dependent objects
      this.b = b;
      this.n = n;
     // perform further initialization
   }
   ...
  // rest of class.
}

I use setters only for configuration, or when when I need to write / use the JavaBeans pattern - where a 0-args construtor is mandated.

Disadvantages of Constructor Injection

If a class has a lot of dependencies - say more than 5, the constructor method signature starts to get unwieldy. This has to be lived with - however, many dependencies suggests a code smell - and maybe this class should be refactored to separate concerns.

What's the Injector?

Constructor Injection is all well, but leads on to the question - what is the external agent that instantiates and supplies dependencies?

Roll your own

In the most trivial of cases, it can just be the static main method - which instantiates each of the objects, and plumbs them all together. This simple approach has the following limitations:

  • Soon becomes unwieldy when the application grows to large numbers and types of objects.
  • Requires additional work if objects need to be created lazily / dynamically during the execution of the program
    • Factory code needs to be kept around, and provided to objects so that new objects can be instantiated as needed
  • Hard to construct object structures with cyclic dependencies.
    • Some kind of proxy / adapter is required to break the cycle.

Use a container library

There's a number of dependency-injection container systems which are better to use once your application grows beyond a certain size.

Sadly, there isn't one in the Java standard libraries - and I'm not aware of a JSR to standardize these. Which is a pity, because theres many commonalities between them. The usual mode of operation is

  • Instantiate the container
  • Tell the container about your implementation objects - for each the
    • public interface
    • implementation class
    • Dependencies (optional, sometimes deduced from constructor signature)
    • Configuration (what properties to set, or config data to pass in)
  • Instruct the container to 'start', which insantiates certain objects.

At this point, you have a running container. It's usually possible to query the container for objects - although this might be considered bad form. If there's something outside the container that needs access to the managed objects, shouldn't it be inside the container itself, and have whatever it needs injected like everything else? This is a design call - where to draw the line between what is inside the container, and what code remains outside.

In VODesktop as much is kept inside the container as possible - Outside the container there is only the startup code that parses commandline options and creates the container.

PicoContainer

Some containers, like PicoContainer , are intentionally very simple. The registration of implementation objects is done programatically using methods like pico.register(Class iface, Class implementation) . In these systems dependencies are computed automatically - which only works with constructor injection. There's little provision for setter-injection or configuration.

I've previously used PicoContainer in the CEA-server and JES implementations. I also used it in an early version of AstroRuntime - but I soon reached its limits.

Spring

I looked at other dependency-injection containers. The most famous is Spring - but I was put off by it's complexity and download size - it seems to be better suited for application-server-type server-side installations. It comes with many pre-defined components / aspects - so that adding persistence, web services, transactions, etc to your objects is just a matter of configuration. Few of these pre-defined aspects were of use to AR.

HiveMind

So I settled on HiveMind - a middle-weight container. It was developed by apache primarily as the underpinnings of Tapestry - a web application framework - but has also been used in other settings - notably in client-side Swing applications.

Hivemind out-of-the-box comes with very few predefined components or aspects. Instead, it's very extensible and quite straightforward to do so. There's a sister project called Hivemind-Utils that defines additional some components to support UI, locking and different ways of constructing objects. However, often it's simplest to extend by hand so that the solution precisely matches the application's needs.

This versatility leads to a hivemind system that is tailored just to the application it's being used within. This means that there is less commonality between hivemind systems than there might be between Spring systems in the same usage, although it is much clearer than picocontainer, and download bulk and startup time are more easily controlled.