Pages

Wednesday, November 17, 2010

Spring Dependency Injection

Without the concept of dependency injection, a consumer who needs a particular service in order to accomplish a certain task would be responsible for handling the life-cycle (instantiating, opening and closing streams, disposing, etc.) of that service. Using the concept of dependency injection, however, the life-cycle of a service is handled by a dependency provider (typically a container) rather than the consumer. The consumer would thus only need a reference to an implementation of the service that it needed in order to accomplish the necessary task.

A kind of Inversion of Control (IoC)

● “Hollywood Principle” – Don't call me, I'll call you

● “Container” resolves (injects) dependencies of components by setting implementation object (push)

● As opposed to component instantiating or Service Locator pattern where component locates implementation (pull)

● Martin Fowler calls Dependency Injection

EJB Vs Spring

EJB are Java EE platform components where we can get the benefits of life cycle management, bean management, transaction management and security Services. EJB's are called heavy weight components due to their complexity.

Many lightweight components are designed to overcome the shortcomings of EJB. They lightweight in that they can support simple Java objects as components. A challenge of lightweight containers is how to decouple dependencies between components. IOC has proved to be an effective solution to this problem. Where IOC is a general design principle DI is a concrete design pattern the embodies this principle.

Example 1

Problem: Design an application for generating different types of reports (text, pdf and html reports) for different time periods (daily, monthly and quarterly).


Solution:


public interface ReportGenerator {
    public void generateReport(String reportContent);
}

public class TextReportGenerator implements ReportGenerator{
    public void generateReport(String reportContent) {
        System.out.println("Text Report:" + reportContent);
    }
}

public class HtmlReportGenerator implements ReportGenerator{
    public void generateReport(String reportContent) {
        System.out.println("HTML Report:" + reportContent);
    }
}

public class PdfReportGenerator implements ReportGenerator{
    public void generateReport(String reportContent) {
        System.out.println("PDF Report:" + reportContent);
    }
}

public class ReportService {
    private ReportGenerator reportGenerator = new TextReportGenerator(); // Service is directly contacting concrete class of ReportGenerator here.

    public void dailyReport() {
        reportGenerator.generateReport("Daily Report Content");
    }

    public void monthlyReport() {
        reportGenerator.generateReport("Monthly Report Content");
    }

    public void quarterlyReport() {
        reportGenerator.generateReport("Quarterly Report Content");
    }
}


ReportService is creating the instance of ReportGenerator internally, so it has to be aware of which concrete class of ReportGenerator to use. This will cause a direct dependency fromReportService to either of the ReportGenerator implementations.

To avoid dependency between these I am going to introduce container:

import java.util.HashMap;
import java.util.Map;

public class Container {
    public static Container instance;
    private Map<String, Object> components;

    public Container() {
        components = new HashMap<String, Object>();

        instance = this;

        ReportGenerator reportGenerator = new TextReportGenerator();
        components.put("reportGenerator", reportGenerator);

        ReportService reportService = new ReportService();
        components.put("reportService", reportService);
    }

    public Object getComponent(String id) {
        return components.get(id);
    }
}

Now I am going to change the ReportService class to:

public class ReportService {
    private ReportGenerator reportGenerator = (ReportGenerator)Container.instance.getComponent("reportGenerator");
    ...
}

This modification means that ReportService doesn’t have to worry about which ReportGenerator implementation to use, so you don’t have tomodify ReportService any more when you want to switch report generator implementation.

Now we will test our code by writing main class:

public class TestReport {
    public static void main(String[] args) {
        Container container = new Container();
        ReportService reportService =
                (ReportService) container.getComponent("reportService");
        reportService.dailyReport();
    }
}

Result is: Text Report:Daily Report Content

conclusion: employing a container can help reduce coupling between different components within a system, and hence increase the independence and re usability of each component. In this way, you are actually separating configuration (e.g., which type of report generator to use) from programming logic (e.g., how to generate a report in PDF format) in order to promote overall system re usability.

Using a Service Locator to Reduce Lookup Complexity:

To reduce the lookup complexity of your components, you can apply one of Sun’s core Java EE design patterns, Service Locator. The idea behind this pattern is as simple as using a service locator to encapsulate the complex lookup logic, while exposing simplemethods for lookup. Then, any component can delegate lookup requests to this service locator.

Now we will create simple ServiceLocator class and move the lookup logic of ReportService into this class.

public class ServiceLocator {
    private static Container container = Container.instance;

    public static ReportGenerator getReportGenerator() {
        return (ReportGenerator) container.getComponent("reportGenerator");
    }
}

Now change the ReportService class to:

public class ReportService {
    private ReportGenerator reportGenerator = ServiceLocator.getReportGenerator();
    ...
}

Applying Inversion of Control and Dependency Injection (Now we will finally apply the DI pattern to remove the dependency between ReportService and ServiceLocator.

The idea of this principle is to invert the direction of resource retrieval. In a traditional lookup, components seek resources by making requests to a container, and the container duly returns the resources in question.With IoC, the container itself actively delivers resources to its managed components. A component has only to choose a way of accepting the resources.

Now we will change our ReportService class like below:

public class ReportService {
    private ReportGenerator reportGenerator; //No need to lookup, container is responsible to supply this.

    public void setReportGenerator(ReportGenerator reportGenerator) {
        this.reportGenerator = reportGenerator;
    }
   
    ...

}

Now our Container class looks like below:

import java.util.HashMap;
import java.util.Map;

public class Container {

    private Map<String, Object> components;

    public Container() {
        components = new HashMap<String, Object>();

        ReportGenerator reportGenerator = new TextReportGenerator();
        components.put("reportGenerator", reportGenerator);

        ReportService reportService = new ReportService();
        reportService.setReportGenerator(reportGenerator);
        components.put("reportService", reportService);
    }

    public Object getComponent(String id) {
        return components.get(id);
    }
}


-> Now we can completely remove the ServiceLocator here because we are no more using that.
-> Service classes are completely independent of dependencies, container is responsible for supplying the dependencies.
-> Spring Container uses same technique to provide dependencies.



Different Types of Dependency Injection

Injecting a dependency via a setter method is not the only way of implementing DI. You will need different types of DI in different scenarios.

There are three main types of DI:

• Interface injection (Type 1 IoC)
• Setter injection (Type 2 IoC)
• Constructor injection (Type 3 IoC)

Setter and Constructor Injections are most widely used once. Interface injection is seldom used.


Hope it is helpful and Happy Learning!

No comments:

Post a Comment