Friday, May 14, 2010

Implementing your own Dependency Injection

Dependency Injection can help in improving the quality of your code. DI helps writing code that is more flexible, reusable, and easier to test. Many open source frameworks are available that provide Dependency Injection such as Spring Framework, Google Guice, PicoContainer, etc. I, personally, use Spring with most of my projects. However, in some situations you may not be able to use or may not prefer using one of these frameworks. For example, if you are writing a library for other developers to use, you would not think of using a DI framework (true in my case, I'm a member of SMSLib project). Using a DI framework in a library is not a good idea, since this widens the library's learning curve. In addition, some users may have their own DI framework that might not be compatible with the DI framework used by the library. Usually, DI frameworks are associated with IoC and are meant to be used by end developers which make use of Ioc and DI to integrate different libraries, in addition to their own code, to provide applications for end users. In these cases, where using a DI framework is not an option, you usually end up implementing your own DI for your code.

Simple Dependency Injection Implementation

Commonly, Factory Design patterns are used to hide instantiation details. These details include instantiation of the implementation choice, that the factory should provide, and it's dependencies. In most cases this pattern is sufficient for instantiating classes and wiring dependencies. One problem with this pattern, though, is that it could be hard to implement and is easily affected by changes to the implementation choice and it's dependencies. Some changes to implementation choice and/or it's dependencies may require modifications to the Factory class. Another problem is that its hard to imagine all possible implementations that this factory should be able to instantiate. A new implementation may require a different set of dependencies which requires adding new code to factory class in order to handle this new implementation. The last problem with this pattern is that its not reusable, you can not use the same factory class to handle different components or set of classes in your code.

What we need to have is a Dependency Injection Implementation that is simple to implement, is not affected by code changes and is reusable. A simplified implementation that is not as powerful as DI frameworks yet provides basic requirements of instantiating code and wiring dependencies. In the rest of this post I'll discuss about writing such an implementation.

Similar to most DI frameworks, this implementation will use Java Annotation. Instead of writing code that defines and wires dependencies at once, we can leave the definition part to annotations and write code for wiring. Parts of code that require dependency injection should be annotated. We will start by defining an Annotation that will be used for defining dependencies.

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD})
public @interface Wire {
String value();
}


The value of the @Retention annotation specifies that this annotation type can be read at runtime. The @Target values define where this annotation can be used, which are fields and methods. The value() method indicates that this annotation requires a String value such as @Wire("anObjectName"). This String value will be used to identify an object.

In order to demonstrate how this Annotation is used, let's see a simple example that requires DI. The example that I will use here is a MessageService component which can be a part of a bigger application. The MessageService is responsible of sending messages to customers.

- Can send messages to customers using email (maybe SMS in the future).
- All sent messages should be saved in file system or Database.
- Messages could be scheduled for future delivery. These messages should be saved (file system or Database) until delivered.

Let's start by defining required classes. First, the MessageService class which is the main class in this component.
public class MessageService {
@Wire("sentStore")
protected AbstractMessageStorage storage;
@Wire("gateway")
protected AbstractGateway gateway;

public void sendMessage(Message message);

}


The MessageService class has two dependencies. AbstractGateway (to send messages) and AbstractMessageStorage (to save sent messages). Both dependencies are annotated using @Wire and are given names. Since the annotations are used on the fields, both dependencies will be injected using field injection.

The AbstractGateway class which will be extended by MailGateway (sends messages through email).
public abstract class AbstractGateway {

protected AbstractMessageStorage storage;
public abstract boolean sendMessage(Message message);

@Wire("pendingStore")
private void setStorage(AbstractMessageStorage storage) {
this.storage = storage;
}
}

The AbstractGateway class has one dependency AbstractMessageStorage (to save scheduled messages). Since the annotation is used on the method, dependency will be injected using method injection.

The MailGateway class has one dependency which is inherited from AbstractGateway and does not require any additional dependencies.


The AbstractMessageStorage class which will be extended by DefaultMessageStorage (saves messages in memory) FileMessageStorage (saves messages in file system) and DBMessageStorage (saves messages in DB)
public abstract class AbstractMessageStorage {

public abstract void saveMessage(Message message);
public abstract List getMessages();
}


Now, let's see how we can load our classes and inject dependencies. SimpleContext class is responsible of loading objects, injecting dependencies, and saving loaded objects inside a HashMap. The most important method in SimpleContext is the load method which accepts a Properties object as a parameter.
public void load(Properties properties){
}


The properties parameter should contain information about the objects that need to be loaded, each key/value property should be represented as objectName/objectClass. For example, a key "sentStore" with the value "com.simpledi.store.DBMessageStorage". This key/value indicates that an object of type com.simpledi.store.DBMessageStorage should be created and is identified as sentStore. Any field or method that are annotated with @Wire("sentStore") will be injected with this object.

The first lines in the load method iterate over the passed properties and create a instance of each class and save them in HashMap for latter processing.
// First create a instance of each class
for(Object key:properties.keySet()){
instances.put(key.toString(), Class.forName(properties.getProperty(key.toString())).newInstance());
}

After creating the objects and saving them, each object's class is checked for any @Wire annotation that may be declared and dependencies are injected accordingly.
// For all instances find fields & methods that have a Wire annotation and set their dependency
for(String key:instances.keySet()){
Object instance=instances.get(key);
// get all fields & methods of the class and it's super classes that are marked with Wire annotation
List  members=getWiredFieldsAndMethods(new ArrayList(),instance.getClass(),Object.class);
// inject dependencies
for (Member member : members) {
if(member instanceof Field){
inject((Field) member,key,instance);
}else if(member instanceof Method){
inject((Method) member,key,instance);
}else{
throw new RuntimeException("Error injecting dependency to "+key+": Member type not supported.");
}

}
}

The following method injects field level dependencies.
private void inject(Field field,String name,Object instance){
Wire wire= field.getAnnotation(Wire.class);
field.setAccessible(true);
Object dependency=instances.get(wire.value());
if(dependency==null){
// Fail Fast
// Could be injected with a null value but failing fast is better
throw new RuntimeException("Dependency "+wire.value()+" for "+name+" was not found.");
}
field.set(instance, dependency);
}

The following method injects method level dependencies.
private void inject(Method method,String name,Object instance){
Wire wire= method.getAnnotation(Wire.class);
method.setAccessible(true);
Object dependency=instances.get(wire.value());
if(dependency==null){
// Fail Fast
// Could be injected with a null value but failing fast is better
throw new RuntimeException("Dependency "+wire.value()+" for "+name+" was not found.");
}
method.invoke(instance, dependency);
}

The name of dependency object is determined from the @Wire.value() method. The name of dependency object is used to get the object from the HashMap which is then injected into the field or method. If the dependency was not found a RuntimeException is thrown.

Note that field.setAccessible(true) and method.setAccessible(true) are needed in case the field or method is not public. These methods will throw SecurityException in case a SecurityManager is present and permissions for this operation was denied, for more details refer to javadoc.

To test the code we will write some code that invokes it using different configurations.
// Define a MessageService that uses MailGateway, DBMessageStorage (for saving sent Messages),
// and DefaultMessageStorage (for saving pending Messages)

Properties properties=new Properties();
properties.setProperty("messageService", "com.simpledi.DefaultMessageService");
properties.setProperty("gateway", "com.simpledi.gateway.MailGateway");
properties.setProperty("sentStore", "com.simpledi.store.DBMessageStorage");
properties.setProperty("pendingStore", "com.simpledi.store.DefaultMessageStorage");

SimpleContext context = SimpleContext.getInstance();
//load properties
context.load(properties);

// Get the MessageService instance
AbstractMessageService service=context.getObject(AbstractMessageService.class);
// Send a message
service.sendMessage(new Message("message"));
// Send a scheduled message
service.sendMessage(new Message("delayed message",new Date(System.currentTimeMillis()+1000)));

The output will look similar to this

Mail Gateway: Message "message" sent
DB Store: Message: "message" stored

Mail Gateway: Storing message: "delayed message" for later delivery
Default Store: Message: "delayed message" stored

Changing the above properties can produce the following output
Mail Gateway: Message "message" sent
DB Store: Message: "message" stored

Mail Gateway: Storing message: "delayed message" for later delivery
File Store: Message: "delayed message" stored


Now, imagine that after a period of time, we needed to add the ability to send messages through SMS. So, we will add an SMSGateway class that extends AbstractGateway. SMS messages will be sent through a modem and we need to use different modem types. Since we need to write specific code to work with specific modem types it will be hard to put all this code in SMSGateway. The best solution is to add a ModemDriver class or interface to act as base class for all drivers implementations and which can be use by SMSGateway.
public class SMSGateway extends AbstractGateway {

@Wire("modem")
private ModemDriver modem;

@Override
public boolean sendMessage(Message message) {
// sending code here
}
}

In addition to SMSGateway a ModemDriver interface and an implementation (GenericModemDriver) will also be added. I did not include them here.

Now, let's modify our previous test code. Of course, if the properties were loaded from a file no modifications to the code would have been needed.

We only need to modify
properties.setProperty("gateway", "com.simpledi.gateway.SMSGateway");

and add a new property for modem
properties.setProperty("modem", "com.simpledi.gateway.GenericModemDriver");

The output will look similar to this

SMS Gateway: Message "message" sent, through Generic Modem Driver
File Store: Message: "message" stored

SMS Gateway: Storing message: "delayed message" for later delivery
Default Store: Message: "delayed message" stored

Note that we added a new AbstractGateway implementation that requires additional dependencies with no modifications to SimpleContext. If we were using a factory class we would have had written additional code to handle the new implementation and it's dependencies.

In conclusion
The above DI implementation is considered a basic DI implementation which may be sufficient for basic DI needs. However, there are some limitations in this implementation. The following are some of these limitations

- All classes should have no constructors or one empty constructor.
- No support for constructor level injection.
- Method injections are limited to methods with only one parameter.
- SecurityException might be thrown, if a SecurityManager is present and permissions are denied.

In my next post, I will try to improve this implementation to overcome some of the above limitations.

Source Code
The full source code can be downloaded here. Feel free to use and/or modify the code. Note that this code was never used or tested in a real project.

5 comments:

  1. what is the impimentaion of getWiredFieldsAndMethods method??

    ReplyDelete
  2. The getWiredFieldsAndMethods method gets a list of fields and methods that are annotated with @Wire. If you would like to see it's implementation, download the source code.

    ReplyDelete
  3. thank you very much did'nt see the download link.

    ReplyDelete
  4. Very important tutorial

    ReplyDelete
  5. great.

    Usually, I use put config in the DB,
    key class
    ---------------------------
    modem com.test.Modem.class
    wire com.test.wire.class

    and from the key, use factory pattern to load the class.

    ReplyDelete