- Dependency Injection
- Dependency Injection Containers
- Dependency Injection Benefits
- When to use Dependency Injection
- Is Dependency Injection Replacing the Factory Patterns?
- The Opportunities Missed by Annotation Based Dependency Injection
- Design of a Dependency Injection (DI) Container
- Butterfly Container Script - Design Considerations
- Butterfly DI Container - Internal Design
- Butterfly DI Container - Global and Local Factories
Butterfly DI Container - Internal Design
The internal design of Butterfly DI Container runtime is centered around two design patterns:
- Factory Pattern
- 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.
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:
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
first a new
MyBean instance is created, then the
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
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
on that instance. Here is how it looks:
f2 factory returns whatever the
If the method returns
void, the factory returns the object
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:
f1, f2 and
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
parent. If you request a
instance from the container (
the call sequence will look like this:
You can obtain objects directly from both the
factory, but notice how the
child factory obtains an object from the
factory, injects it into a
com.jenkov.Child object, and returns the
object. The factories
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.