A Survey on Dependency Injection

Posted on 5 December 2012

1


One of the most important aim of any software architecture should be trying to reduce the coupling among the different components in order to maximize their reuse. Two components are said to be coupled if one of the two (i.e. ClassA) depends on the other (i.e. ClassB).

In a badly defined architecture, with widespread component instantiations, if you wanted to change a component implementation you may need to dive into the code to find and change all of its instantiations. This is bad as it is boring and error prone.

Consider the two following classes. The former, ClassA, depends on the latter, ClassB.

public class ClassA {

	ClassB bInstance;

	public ClassA(){
		bInstance = new ClassB();
	}

	public void methodA(){
		System.out.println("A Method");
		bInstance.methodB();
	}

}

The ClassB instance is created in the ClassA’s constructor.

public class ClassB {
	public int methodB() {
		System.out.println("actual method");
		return 0;
	}

}

Suppose now want to test the ClassA implementation alone. To this end we want to change the ClassA dependency to ClassB with a dependency to a mock class, i.e. MockClassB.

We therefore need to change the ClassA code and modify the dependency and the ClassB instantiation.

public class MockClassB {

	public MockClassB(){

	}
	public int methodB(){
		System.out.println("invoking mock method");
		return 1;
	}

}

On a bigger system  you can easily imagine how hard could be changing a component type.

In the next sections we are going to see what solutions can be applied to mitigate this issue.

The Classic approch: A Factory Class

The first and most traditional approach is to rely on Factory classes. According to the Factory Design Pattern, a Factory is a class responsible for the instantiation of different components realizing the same interface. A factory can be implemented by means of a Singleton or just static methods.

The first important thing is thus to use Interfaces. Thanks to interfaces we can in fact exploit, in an elegant way, one of the most powerful aspects of object orientation: polymorphism.

public interface InterfaceB {
	public int methodB();
}

In the following example we now see ClassA depends on InterfaceB. Which is an Interface implemented by both ClassB and a its possible alternatives such as MockClassB.

public class ClassB implements InterfaceB {
 @Override
 public int methodB() {
System.out.println("actual method");
return 0;
 }

}

 

public class MockClassB implements InterfaceB{

	public MockClassB(){

	}
	@Override
	public int methodB(){
		System.out.println("invoking mock method");
		return 1;
	}

}

We can now define a class, we called BFactory, dedicated to the instantiation of objects implementing the InterfaceB interface. We define a private constructor as we are not interested in instantiating this class. We just need to invoke two methods, one to specify the kind of object to instantiate and one method to make the actual instantiations. To this end we rely on the Java’s support to reflection and define a setBClass method which takes as parameter an object representing the Class of the objects the factory will instantiate. The class passed as parameter is saved by the factory in dedicated static attribute. The getBInstance() method just returns an instance of the stored class.

public class BFactory {

private BFactory(){}
    private static Class bClass;
    public static void setBClass(Class val){
    bClass=val;
}
public static InterfaceB getBInstance(){
    try {
       return (InterfaceB) bClass.newInstance();
    } catch (InstantiationException e) {
       // TODO Auto-generated catch block
       e.printStackTrace();
    } catch (IllegalAccessException e) {
       // TODO Auto-generated catch block
       e.printStackTrace();
    }
    return null;
}

}

Along the code we do not instantiate the ClassB or MockClassB classes but exploit the factory to retrieve the InterfaceB instance we need.

public class ClassA {

	InterfaceB bInstance;

	public ClassA(){
		bInstance = BFactory.getBInstance();
	}

	public void methodA(){
		System.out.println("A Method");
		bInstance.methodB();
	}

}

Thanks to this approach we can modify from a single point the kind of InterfaceB instance we need.

public class Main {
	public static void main(String[] args) {
        BFactory.setBClass(MockClassB.<wbr />class);
//but could have been..
        //BFactory.setBClass(ClassB.class);

ClassA aInstance= new ClassA();

aInstance.methodA();
	}

}

Google Guice

Google Guice is an open source framework, by Google, specifically designed to help developers in implementing dependency injection.

Guice is based on the use of Java annotations. You have to tag the constructors in which dependencies shall be injected with the @Inject annotation.

import com.google.inject.Inject;
public class ClassA {

InterfaceB bInstance;

@Inject
public ClassA(InterfaceB b){
bInstance = b;
}

public void methodA(){
System.out.println("A Method");
bInstance.methodB();
}

Then you have to define a number of Guice modules which are Java classes extending the AbstractModule class. Each module represents a different configuration of classes implementing the injected interfaces. A configuration consists of a set of bindings between the interfaces and the classes to instantiate.

In this example we only define two modules. The first one simply consists of one binding relating InterfaceB to ClassB.

import com.google.inject.AbstractModule;
public class DefaultModule extends AbstractModule {

	@Override
	protected void configure() {
		// TODO Auto-generated method stub
		bind(InterfaceB.class).to(ClassB.class);

	}

}

In the second module, called TestModule, we instead bind the InterfaceB to the MockClassB.

import com.google.inject.AbstractModule;
public class TestModule extends AbstractModule {

	@Override
	protected void configure() {
		// TODO Auto-generated method stub
		bind(InterfaceB.class).to(MockClassB.class);

	}

}

When you need to instantiate an object:

  1. you use the Guice factory to create an injector. The injector is created with a context given by the module you want to use passed as parameter to the createInjector method of the Guice factory.
  2. you use the injector to create instances of the class passed as parameter. The injector creates the instance by injecting objects in its constructor according to the related module.
import com.google.inject.Guice;
import com.google.inject.Injector;
public class Main {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		Injector injector = Guice.createInjector(new DefaultModule());
                //or
                //Injector injector = Guice.createInjector(new TestModule());
	    ClassA aInstance = injector.getInstance(ClassA.class);
	    aInstance.methodA();

	}

}

Guice provides many other possibilities which, for the sake of room, we do not explain here. The interested reader can check the Google Guice website .

Spring

Spring is a framework for Enterprise Application which realizes a container for Dependency Injection. You can use it for the development of stand alone application as well and this is what we are going to do.
In Spring a configuration is described in XML files like the following one that we called testConfiguration.xml

</pre>
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

 <bean id="bObject" class="MockClassB"/>

 <bean id="aClass" class="ClassA">
 <constructor-arg ref="bObject"/>
 </bean>
</beans>

As you can see we defined a bean with id “bObject” which corresponds to the MockClassB class. Then we said the bean corresponding to ClassA shall be instantiated by passing as constructor parameter the bObject bean, which in this configuration corresponds to MockClassB.
When we need to instantiate objects we first create an ApplicationContext, a special Spring class which plays the role of factory for the objects that shall be created according to the desired configuration.
In the following example we instantiated an ApplicationContext by means of a ClassPathXmlApplicationContext which extends ApplicationContext and taks as input a configuration XML describing the context configuration itself. We can therefore use the getBean method, passing the ID of the desired bean, to get a ClassA instance conform to the loaded configuration. If we need to switch the injected objects in the whole system we just have to provide another context which means changing the XML passed to the context instantiation.

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;

public class Main {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println("test");
		ApplicationContext context= new ClassPathXmlApplicationContext("testConfiguration.xml");
		ClassA aInstance= (ClassA) context.getBean("aClass");

		aInstance.methodA();

	}

}

Aspect Oriented Programming

I finally want to show you an alternative approach based on aspect oriented programming (AOP) I personally find interesting.

I look at AOP as a way to make model transformations (or should I say code to code transformation?) at code level. You may therefore exploit aspects to transform specific portions of your code in order to change their initial behaviors.

In this case we are interested in finding and modifying specific classes instantiations and change them.
To this end we first need a hook to catch through the pointcut of our aspect.
In our example we may for instance define a dedicated Java Interface called InterfaceBInjector providing an injectInterfaceB() method each class having a dependency to an InterfaceB object will use to retrieve an actual instance.

public interface InterfaceBInjector {
     InterfaceB injectInterfaceB();
}

ClassA shall therefore implements this interface, and provide a default implementation for its method.


public class ClassA implements InterfaceBInjector{

InterfaceB bInstance;

public ClassA(){
      bInstance = injectInterfaceB();
}

public void methodA(){
     System.out.println("A Method");
     bInstance.methodB();
}

@Override
   public InterfaceB injectInterfaceB() {
      new ClassB();
   }
}

Just to make things a bit more interesting suppose we have another client class, called YAClassA (Yet Another ClassA) which, for the sake of simplicity, extends hte ClassA class.

public class YAClassA extends ClassA{

    YAClassA(){super();}

}

We now define the aspect (via AspectJ) which consists of one pointcut, called injector, that intercepts the invokation of any injectInterfaceB() method and exposes as context the invoking object (in the example referred to as instance). The aspect also provides one advice which wraps (around) the pointcut and provides a possibly different behavior for it. In this simple example we change the default instantiation if the InterfaceB object is instantiated by a ClassA instance, otherwise we return the default instantiation ( the proceed() keyword means execute the pointcut as is, thus in this case instantiates the default InterfaceB object). In this way we also show how the dependency injection may be context dependent.

public aspect InterfaceBInjectionConfigurati<wbr />on {

pointcut injector(Object instance):
       target(instance) && call(InterfaceB *.injectInterfaceB());

       InterfaceB around(Object instance):  injector(instance){
           if (instance.getClass()==ClassA.<wbr />class){
               return new MockClassB();
           }else{
               return proceed();
          }
       }
}

We do not need to modify anything in the main code as it will be the AspectJ weaver to do the work for us.


public class Main {

/**
* @param args
*/
public static void main(String[] args) {

    ClassA aInstance= new ClassA();
    ClassA yaInstance = new YAClassA();
    aInstance.methodA();
    yaInstance.methodA();
}

}

This is the output of the Console when executing this simple example

A Method
invoking mock method
A Method
actual method
About these ads
Posted in: Java