Tech and Media Labs
This site uses cookies to improve the user experience.




Butterfly DI Container - Internal Design

Jakob Jenkov
Last update: 2014-05-25

NOTE: This text is part 3 in a series on the "Design of a DI Container". To be notified of new texts in this (and other) tutorial trails, Subscribe to the RSS feed.

The internal design of Butterfly DI Container runtime is centered around two design patterns:

  1. Factory Pattern
  2. Pipes and Filters Pattern

When combined together these two patterns make up a new kind of pattern which I call "Chained Factories".

NOTE: As you read through this design you may think that all the factories created and linked internally must lead to a really slow dependency injection container. It doesn't ! Butterfly Container has been measured against Google Guice, and beats it with a large margin. Guice again claims to beat Spring by a large margin, so Butterfly Container is definately among the fastest Java DI containers around! Not bad, considering this design also yields one of the most flexible Java DI containers around.

Factory Pattern

A DI container is capable of producing object instances of whatever types (classes) you configure the container to. In Butterfly Container the concrete object is returned by a factory kept internally in the container. Some factories, like singleton factories, may cache object instances rather than instantiate a new object for each call to the factory.

Here is how the call sequence looks:



Pipes and Filters Pattern

A pipe is something you can either read from, or write to, just like IO streams. A pipe can be wrapped in a filter. The filter itself also looks like a pipe, meaning it too can be wrapped in a filter. This way you can create a chain of pipes and filters, each modifying the data passed down through the chain.

Here is a simple call sequence example:



Chained Factories

One of the main features of a DI container is that the output of one factory can be injected into the output of other factories. In other words, if a factory produces instances of A, you can inject instances of B into these A instances. In Butterfly Container this is done by chaining the factories. Let us look at a simple example configuration:

myBean = * com.jenkov.MyBean().setValue("someValue");

When the container is asked for an instance from the myBean factory, first a new MyBean instance is created, then the setValue() method is called on that instance.

Internally this configuration is divided into two factories (actually more, but I will get back to that later). One factory (f1) that creates the MyBean instance, and a another factory (f2) that calls setValue(...) on the MyBean instance returned from f1. Here is how it looks as a call sequence :



In reality the sequence of the chained factory 1 and factory 2 is reversed. The container only knows about factory 2 (the last factory in the chain). Only factory 2 knows about factory 1. Factory 2 knows it needs to obtain an instance from factory 1, and call setValue() on that instance. Here is how it looks:



The f2 factory returns whatever the setValue() method returns.

If the method returns void, the factory returns the object the setMethod() was invoked on. This makes it possible to chain method calls on methods returning void. Like this configuration illustrates:

bean = * com.jenkov.AnObject().setValue1("value1")
                              .setValue2("value2")
                              .setValue3("value3");

Internally the local factory chain looks like this:



The factories f1, f2 and f3 all return the AnObject instance they called their methods on, since their setter-methods return void. This makes it possible for the next factory in the chain to call methods on the same object.

It is also possible to call one named factory from within another, like this:

parent = * com.jenkov.Parent();
child  = * com.jenkov.Child(parent);

In the above example the factory child references the factory parent. If you request a child instance from the container (container.instance("child")) the call sequence will look like this:



You can obtain objects directly from both the parent and child factory, but notice how the child factory obtains an object from the parent factory, injects it into a com.jenkov.Child object, and returns the Child object. The factories parent and child are chained!

The factory chains can be quite large, and can be thought of as a large factory graph - a graph of factories calling each other, the output of one factory being the input of another factory, each factory either refining or wrapping the product obtained from other factories.

There is a lot more going on internally than you have seen here. Each value, e.g. the value "123Text" is also encapsualted in a factory, and there are different types of factories as well. All this will be covered in more detail in the next text in the series. See the link below.

Jakob Jenkov




Copyright  Jenkov Aps
Close TOC