Explain Dependency Injection in Spring

Dependency Injection (DI) refers to the process of supplying an external dependency to a software component. DI can help make your code architecturally pure. It aids in design by interface as well as test-driven development by providing a consistent way to inject dependencies. For example, a data access object (DAO) may depend on a database connection. Instead of looking up the database connection with JNDI, you could inject it. 

One way to think about a DI container like Spring is to think of JNDI turned inside out. Instead of an object looking up other objects that it needs to get its job done (dependencies), a DI container injects those dependent objects. This is the so-called Hollywood Principle, “Dont call us” (lookup objects), “we’ll call you” (inject objects)

Lets say that you have an automated teller machine (ATM) and it needs the ability to talk to a bank. It uses what it calls a transport object to do this. In this example, a transport object handles the low-level communication to the bank. 

This example could be represented by either of the  two interfaces as follows:

AutomatedTellerMachine interface

package com.tuturself.springdependencyinjection; 
import java.math.BigDecimal; 
public interface AutomatedTellerMachine {
        void deposit(BigDecimal bd);
        void withdraw(BigDecimal bd);
}

ATMTransport interface

package com.tuturself.springdependencyinjection; 
public interface ATMTransport {
        void communicateWithBank(byte [] datapacket);
}

Now the AutomatedTellerMachine needs a transport to perform its intent, namely withdraw money and deposit money. To carry out these tasks, the AutomatedTellerMachine may depend on many objects and collaborates with its dependencies to complete the work.

An implementation of the AutomatedTellerMachine may look like this:

AutomatedTellerMachine implementation:

package com.tuturself.springdependencyinjection; 
import java.math.BigDecimal; 
public class AutomatedTellerMachineImpl implements AutomatedTellerMachine{
        private ATMTransport transport;
        public void deposit(BigDecimal bd) {
          ...
                transport.communicateWithBank(...);
        }
        public void withdraw(BigDecimal bd) {
          ...
                transport.communicateWithBank(...);
        }
        public void setTransport(ATMTransport transport) {
                this.transport = transport;
        } 
}

The AutomatedTellerMachineImpl does not know or care how the transport withdraws and deposits money from the bank. This level of indirection allows us to replace the transport with different implementations such as in the following example:

Three example transports: SoapAtmTransport, StandardAtmTransport and SimulationAtmTransport

package com.tuturself.springdependencyinjection; 
public class SoapAtmTransport implements ATMTransport { 
        public void communicateWithBank(byte[] datapacket) {
           ...
        }
}
package com.tuturself.springdependencyinjection; 
public class StandardAtmTransport implements ATMTransport { 
        public void communicateWithBank(byte[] datapacket) {
          ...
        } 
}
package com.tuturself.springdependencyinjection; 
public class SimulationAtmTransport implements ATMTransport { 
        public void communicateWithBank(byte[] datapacket) {
                ...
        }
}

Notice the possible implementations of the ATMTransport interface. The AutomatedTellerMachineImpl does not know or care which transport it uses. Also, for testing and developing, instead of talking to a real bank, notice that you can use the SimulationAtmTransport.

The concept of DI transcends Spring. Thus, you can accomplish DI without Spring as follows:

DI without Spring

package com.tuturself.springdependencyinjection; 

import java.math.BigDecimal; 
public class AtmMain { 
        public void main (String[] args) {
                AutomatedTellerMachine atm = new AutomatedTellerMachineImpl();
                ATMTransport transport = new SoapAtmTransport();
                /* Inject the transport. */           
                ((AutomatedTellerMachineImpl)atm).setTransport(transport);
                atm.withdraw(new BigDecimal("10.00"));
                atm.deposit(new BigDecimal("100.00"));
        }
}

Then injecting a different transport is a mere matter of calling a different setter method as follows:

Injecting a different dependency

ATMTransport transport = new SimulationAtmTransport();

((AutomatedTellerMachineImpl)atm).setTransport(transport);

To use Spring to inject a dependency you could do the following:

Using Spring to manage dependencies

package com.tuturself.springdependencyinjection;  

import java.math.BigDecimal; 
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; 
public class AtmMain { 
        public static void main (String[] args) {
                ApplicationContext appContext = new ClassPathXmlApplicationContext("classpath:./spring/applicationContext.xml");
                AutomatedTellerMachine atm = (AutomatedTellerMachine) appContext.getBean("atm"); 
                atm.withdraw(new BigDecimal("10.00")); 
                atm.deposit(new BigDecimal("100.00"));
        } 
}
 /spring/applicationContext.xml file
 < ?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-2.5.xsd" > 
         < bean id="atmTransport" class="com.tuturself.springdependencyinjection.SoapAtmTransport" / > 
         < bean id="atm" class="com.tuturself.springdependencyinjection.AutomatedTellerMachineImpl" > 
                 < property name="transport" ref="atmTransport" / > 
         < /bean > 
 < /beans >

The application context is the central interface to the Spring DI container. In the application context, you declare two beans, atmTransport and atm, with a bean tag. Then you use the property tag to inject the atmTransport bean into the transport property. This effectively calls the setter method of the AutomatedTellerMachineImpl transport property (setTransport(...)).

The major capabilities that the application context provides include (taken from API docs):

  • Bean factory methods for accessing application components
  • The ability to load file resources in a generic fashion
  • The ability to resolve messages, supporting internationalization

Using constructor instead of setter

Another option when using Spring is to use constructor arguments instead of setter methods to inject dependencies. This keeps things more pure from an object-oriented design standpoint as an object has to be created with all of its collaborators (a.k.a. dependencies) it needs to fulfill its role. 

Using constructors, injection is much like using setter methods as follows: 

Application context for constructor injection

 < bean id="standardTransport" class="com.tuturself.springdependencyinjection.StandardAtmTransport"/ > 
 < bean id="atm" class="com.tuturself.springdependencyinjection.AutomatedTellerMachineImpl" > 
    < constructor-arg ref="standardTransport" / > 
 < /bean > 

Notice the use of the constructor-arg tag. This implies that the constructor takes transport as a single argument. 

Adding a constructor to AutomatedTellerMachineImpl

public class AutomatedTellerMachineImpl implements AutomatedTellerMachine {  
    private ATMTransport transport; 
    public AutomatedTellerMachineImpl (ATMTransport transport) {
        this.transport = transport;
    }
}

The above example should keep the object purists in your group happy. However, the setter injection style makes test-driven development a bit easier. In practice, the setter method approach is used more often. 

If you have many constructors in the same class with a variable number of arguments, Spring will try to pick the best fit. However, you can give Spring some hints as follows: 

Application context for constructor injection with a hint for Spring

 < bean id="atm" class="com.arcmind.springquickstart.AutomatedTellerMachineImpl" > 
     < constructor-arg index="0" ref="standardTransport" / > 
 < /bean >

Under some circumstances, you can even specify the type attribute that you want Spring to use to resolve the constructor argument in cases when there are more than one possible constructor match. Most times, you dont have to specify index or type.


FOLLOW US ON LinkedIn



Explore Tutu'rself