der kl@mmeraffe | ruby. java. development.

a developers guide to the world of code

Monday, July 31, 2006

Developing an IOC-Container for Dependency Injection Part II

Part I | Part II | Part III

Welcome to the second part. Today we want to define how our configuration file shall look and start with the implementation of our container.
Look at the config-file of my personal dreams:

<services>
 <service name="Customer" class="ioc.example.Customer">
  <property name="id" value="1">
  <property name="names" type="list" value="[12,15,16]">
  <property name="numbers" type="array" value="[a,b,c]">
  <property name="smallmap" type="map" value="[1:a,2:b,3:c]">
  <property reference="User">
  <property reference="Admin">
 </property>

 <service name="User" class="ioc.example.UserImpl">

 <service name="Admin" class="ioc.example.Admin">
  <property name="adminName" value="Sam">
 </property>
</service>

We call every injected class a service. For every service we provide a implementation class. A service can have properties. We don't want to be too extreme in the beginning, so we just want Strings, lists, arrays , maps and references to other classes (via ).
The Details for the configfile above:
Here we define a Bean for the concrete class ioc.example.Customer and mark it with the id Customer. We define some Properties for that Bean:
id is a property that we initialize with the value 1. The container should automatically set the correct type for every primitive. The second property, names, is a list that we initialize with the values 12, 15, 16. It should also be possible to use arrays (for the beginning they'll be automatically String[]) and maps, as you can see at the properties numbers and smallmap. Now to the interesting stuff: The properties User and Admin are referenced beans, defined below in the same file. the Container must take care of all the Dependency Injection Stuff.
As you can see, it shouldn't matter wether you are using a simple concrete class for injection or an implementation of an interface (UserImpl).

So how can we implement the container? First we define the Interface:

package ioc.container;

public interface IOCContainer {
  public Object getInstance(String id);
  public Object getInstanceByClass(String clazz);
  public Object getInstanceByClass(Class clazz);
}

What are we doing here? We need methods to retrieve the auto-wired objects. And we want to have the ability to get them via the name (the id) or the class/class name.

Here comes the Implementation of the Standard IOC Container:


01 /**
02 * Standard Implementation for the IOCContainer
03 */
04 public class IOCContainerStandardImpl implements IOCContainer {
05
06   Map services;
07   IOCConfig conf;
08
09   public IOCContainerStandardImpl(String config) {
10
11     //Read Configuration-XML
12     conf = new IOCConfigParser().parse(config);
13
14     //Create all Services
15     services = new IOCServiceFactory().createServices(conf);
16   }
17
18  /**
19   * Create an Instance with the
20   * id of this class
21   */
22   public Object getInstance(String id) {
23    return services.get(id);
24   }
25
26  /**
27   * Create an Instance with the class
28   */
29   public Object getInstanceByClass(Class clazz) {
30     return services.get(clazz.getName());
31   }
32
33  /**
34   * Create an Instance with the fully
35   * qualified name of the class
36   *
37   * @param clazz
38   * @return
39   */
40   public Object getInstanceByClass(String clazz) {
41     return services.get(clazz);
42   }
43 }


Lets dive into the details:
First, in line 12, we read an configuration xml (like the one above) and parse throught it with an IOCConfigParser class. Here we receive an presentation of our services. Maybe thats not necessary, but that way we can put the configuration in a state and fill it with information that makes it easier to create the "real" services afterwards.
At line 15 we use an IOCServiceFactory class to create all services. That means instantiation of the correct classes, filling them with startup values with correct types defined in the config-file and wiring them together. And this means lots of reflection of course ;-) The services (or the beans, pojos, objects, whatever) are stored as a map in an instance variable. The other methods just give the corresponding service to the caller.

But that should be enough for today. In part 3 we dive deeper and look whats behind the IOCContainer, the Factory and so on. Stay tuned.

add to del.icio.us | submit to digg | submit to reddit

1 Comments:

Post a Comment

Links to this post:

Create a Link

<< Home