Inversion of Control and dependency injection

Inversion of Control (IoC) is a design pattern used in software engineering that facilitates the creation of loosely-coupled systems. In an IoC system, the flow of control is inverted, that is, the program is called by the framework—unlike in normal linear systems where the program calls the libraries. This allows us to circumvent the tight coupling that arises from the control being with the calling program. Dependency injection is a specific case of IoC where the framework provides an assembler or a configurator that provides the user program with the objects that it needs through injection. The user program declares dependencies on other services (provided by the framework or other user programs), and the assembler injects the dependencies into the user program wherever they are needed.

It is important that you clearly understand the concept of dependency injection before we proceed further into the Geronimo architecture, as that is the core concept behind the functioning of the Geronimo kernel and how services are loosely coupled in it. To help you understand the concept more clearly, we will provide a simple example.

Consider the following two classes:

package packtsamples;
public class RentCalculator{
private float rentRate;
private TaxCalculator tCalc;
public RentCalculator(float rate, float taxRate){
rentRate = rate;
tCalc = new ServiceTaxCalculator(taxRate);
}
public void calculateRent(int noOfDays){
float totalRent = noOfDays * rentRate;
float tax = tCalc.calculateTax(totalRent);
totalRent = totalRent + tax;
System.out.println("Rent is:"+totalRent);
}
}
package packtsamples;
public class ServiceTaxCalculator implements TaxCalculator{
private float taxRate;
public ServiceTaxCalculator(float rate){
taxRate = rate;
}
public float calculateTax(float amount){
return (amount * taxRate/100);
}
}
package packtsamples;
public interface TaxCalculator{
public float calculateTax(float amount);
}
package packtsamples;
public class Main {
/**
* @param args. args[0] = taxRate, args[1] = rentRate, args[2] = noOfDays
*/
public static void main(String[] args) {
RentCalculator rc = new RentCalculator(Float.parseFloat(args[1]), Float.parseFloat(args[0]));
rc.calculateRent(Integer.parseInt(args[2]));
}
}

The RentCalculator class calculates the room rent including tax, given the rent rate, and the number of days. The TaxCalculator class calculates the tax on a particular amount, given the tax rate. As you can see from the code snippet given, the RentCalculator class is dependent on the TaxCalculator interface for calculating the tax. In the given sample, the ServiceTaxCalculator class is instantiated inside the RentCalculator class. This makes the two classes tightly coupled, so that we cannot use the RentCalculator with another TaxCalculator implementation. This problem can be solved through dependency injection. If we apply this concept to the previous classes, then the architecture will be slightly different. This is shown in the following code block:

package packtsamples.di;
public class RentCalculator{
private float rentRate;
private TaxCalculator tCalc;
public RentCalculator(float rate, TaxCalculator tCalc){
rentRate = rate;
this.tCalc = tCalc;
}
public void calculateRent(int noOfDays){
float totalRent = noOfDays * rentRate;
float tax = tCalc.calculateTax(totalRent);
totalRent = totalRent + tax;
System.out.println("Rent is:" +totalRent);
}
}
package packtsamples.di;
public class ServiceTaxCalculator implements TaxCalculator{
private float taxRate;
public ServiceTaxCalculator(float rate){
taxRate = rate;
}
public float calculateTax(float amount){
return (amount * taxRate/100);
}
}
package packtsamples.di;
public interface TaxCalculator{
public float calculateTax(float amount);
}

Notice the difference here from the previous implementation. The RentCalculator class has a TaxCalculator argument in its constructor. The RentCalculator then uses this TaxCalculator instance to calculate tax by calling the calculateTax method. You can pass in any implementation, and its calculateTax method will be called. In the following section, we will see how to write the class that will assemble this sample into a working program.

package packtsamples.di;
import java.lang.reflect.InvocationTargetException;
public class Assembler {
private TaxCalculator createTaxCalculator(String className, float taxRate){
TaxCalculator tc = null;
try {
Class cls = Class.forName(className);
tc = (TaxCalculator)cls.getConstructors()[0] .newInstance(taxRate);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return tc;
}
private RentCalculator createRentCalculator(float rate, TaxCalculator tCalc){
return new RentCalculator(rate,tCalc);
}
private void assembleAndExecute(String className, float taxRate, float rentRate, int noOfDays){
TaxCalculator tc = createTaxCalculator(className, taxRate);
createRentCalculator(rentRate, tc).calculateRent(noOfDays);}
/**
*
* @param args args[0] = className, args[1] = taxRate args[2] = rentRate args[3] = noOfDays
*/
public static void main(String[] args){
new Assembler().assembleAndExecute(args[0], Float.parseFloat(args[1]), Float.parseFloat(args[2]), Integer.parseInt(args[3]));
}
}

In the given sample code, you can see that there is a new class called the Assembler. The Assembler, in its main method, invokes the implementation class of TaxCalculator that we want RentCalculator to use. The Assembler then instantiates an instance of RentCalculator, injects the TaxCalculator instance of the type we specify into it, and calls the calculateRent method. Thus the two classes are not tightly coupled and the program control lies with the assembler, unlike in the previous case. Thus there is Inversion of Control happening here, as the framework (Assembler in this case) is controlling the execution of the program. This is a very trivial sample. We can write an assembler class that is more generic and is not even coupled to the interface as in the previous case. This is an example of dependency injection. An injection of this type is called constructor injection, where the assembler injects values through the constructor. You can also have other types of dependency injection, namely setter injection and field injection. In the former, the values are injected into the object by invoking the setter methods that are provided by the class, and in the latter, the values are injected into fields through reflection or some other method. The Apache Geronimo kernel uses both setter injection and constructor injection for resolving dependencies between the different modules or configurations that are deployed in it.

The code for these examples is provided under di-sample in the samples. To build the sample, use the following command:

mvn clean install

To run the sample without dependency injection, use the following command:

java cp di-sample-1.0.jar packtsamples.Main <taxRate> <rentRate> <noOfDays>

To run the sample with dependency injection, use the following command:

java cp di-sample-1.0.jar packtsamples.Assembler packtsamples.di.ServiceTaxCalculator <taxRate> <rentRate> <noOfDays>

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset