Sunday, May 19, 2019

Dependency Injection in Spring Framework

Dependency injection is one of the core concept of Spring framework and it is imperative to understand "What is Dependecy Injection" and how Spring uses it. It is hard to explain it in theory so I'll take a simple example and go step by step to explain what is dependency injection in Spring.


Dependency Injection Concept

As the name suggests Dependency injection means injecting the dependencies but what does that mean? First lets start with understanding what is a dependency. When we create any application there are many classes in it which depend on each other to execute some business logic. In that case any object which needs other objects to do its work, is responsible for getting those dependencies itself.

Let's see it with an example-

Suppose there is a payment service through which you can execute several kind of payments - cash payment, credit card payment, debit card payment etc.

So you create an interface which every Payment class should implement-

public interface IPayment {
    void executePayment();
}

And you create one of the class CashPayment for processing cash payments.

public class CashPayment implements IPayment{
 public void executePayment() {
  System.out.println("Perform Cash Payment"); 
 }
}

Then you create an interface for your Payment Service class and the PaymentService class itself -

public interface IPayService {
 void performPayment();
}
public class PayServiceImpl implements IPayService {
 private CashPayment cashPayment;
 PayServiceImpl(){
  this.cashPayment = new CashPayment();
 }

 public void performPayment() { 
  cashPayment.executePayment();
 }
}

Now you can see the problems here in the PaySerivceImpl class-

  • It is creating its own CashPayment reference, so it is tightly coupled only to cash payment functionality. How about Credit card payment or debit card payment or any other mode of payment?
  • We started well with creating separate classes for separate behaviours and using interfaces but then we introduced CashPayment class itself in PaySerivceImpl, with that one step separation of concern is gone!
  • What if we need to unit test this class? We can't mock its dependencies (i.e. the Payment class) as PayServiceImpl class is tightly coupled to CashPayment class.

Now you know the hazards of tightly coupled dependencies but at the same time you also know that dependencies will always be there in any applications, objects will refer to each other in order to execute the business logic.

So, how about getting those dependencies injected in your code by some other class, in that case objects won't be creating their dependencies themselves but the dependencies will be injected into the objects. Again let's see the same example with some changes to facilitate dependency injection.

How about writing your PayServiceImpl class like this-

public class PayServiceImpl implements IPayService {
 private IPayment payment;
 
 PayServiceImpl(IPayment payment){
  this.payment = payment;
 }
 public void performPayment() {
  payment.executePayment();
 }
}

Just compare it with the previous implementation of PayServiceImpl class.

  • There it was tightly coupled with the specific implementation (CashPayment) of the IPayment interface. In this class it is not tightly coupled with any specific implementation.
  • In this class specific implementation is given (injected) in the constructor of the class.
    IPayService payService = new PayServiceImpl(new CashPayment());
    payService.performPayment();
    
    This will call the CashPayment implementation.
    payService = new PayServiceImpl(new CreditPayment());
    payService.performPayment()
    
    This will call the CreditPayment implementation.
  • Here the code is loosely coupled as PayServiceImpl is not tied to any specific implementation, here it only knows the interface which can easily be swapped by specific implementation at run time as we just saw in point 2

CreditPayment implementation

public class CreditPayment implements IPayment {
    public void executePayment() {
        System.out.println("Performing credit payment");
    }
}

Spring dependency injection

Now we know what dependency injection is and how objects can be injected rather than created by the object itself. So how about having a framework which will manage these associations for you and inject dependencies too. That's where Spring dependency injection comes in the picture.

Spring dependency injection using XML configuration

In Spring framework there are more than one way to manage these associations (term used in Spring is wiring). Here is an example with XML configuration, that should give you an idea how dependency injection works in Spring.

<?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-4.0.xsd">
 
 <!-- defining CashPayment bean -->
  <bean id="cashPaymentBean" class="org.netjs.prog.CashPayment" />
  <!-- Defining PayServiceImpl bean and injecting payment bean -->
  <bean id="paymentBean" class="org.netjs.prog.PayServiceImpl">
      <constructor-arg ref="cashPaymentBean" />
  </bean>
</beans>

Here it can be seen that CashPayment and PayServiceImpl are declared as beans in spring XML configuration. In paymentBean, reference of CashPayment is passed as a constructor argument. With that configuration Spring framework will take care of wiring the associations and injecting the dependencies needed by an object.

If you want to test and see how it is done this class can be used-

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App 
{
    public static void main( String[] args )
    {   
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext
                                                ("appcontext.xml");
        IPayService bean = (IPayService) context.getBean("paymentBean");
        bean.performPayment();
        context.close();
    }
}

Spring dependency injection using annotation

You can also configure the dependency injection using annotations. Configuration using annotations is preferred these days.

In the example @Service and @Component annotations are used with the classes PayServiceImpl and CashPayment to ensure that these classes are discovered while a component scan is done.

Dependency to the payment in PayServiceImpl is defined using the annotation @Autowired. That's how Spring will automatically detect this dependency and inject it.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class PayServiceImpl implements IPayService {
 @Autowired
 private IPayment payment;

 public void performPayment() {
  payment.executePayment();
 }
 
 public IPayment getPayment() {
  return payment;
 }

 public void setPayment(IPayment payment) {
  this.payment = payment;
 }
}
import org.springframework.stereotype.Component;

@Component
public class CashPayment implements IPayment{
 public void executePayment() {
  System.out.println("Perform Cash Payment - ");
 }
}

Now you need to use context:component-scan tag with base-package in the XML. Using base-package you can pass the package where your class resides so that these classes are automatically scanned.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">
 
 <context:component-scan base-package="org.netjs.prog" />
</beans>

You can use the following Java class to run the application which reads the configuration and get the bean to invoke a method.

public class App {
    public static void main( String[] args ){  
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext
         ("appcontext.xml");
        IPayService bean = (IPayService) context.getBean("payServiceImpl");
        bean.performPayment();
        context.close();
    }
}

Types of Dependency Injection in Spring

Dependency Injection in Spring framework exists in two major types-

  • Constructor dependency injection- In this type of DI dependencies are injected using constructors. For setting properties using Constructor DI tag is used.
    Refer Spring Constructor Based Dependency Injection to know more about Constructor DI.
  • Setter dependency injection- In this type of DI dependencies are injected using setter methods. For setting properties using Setter DI tag is used.
    Refer Spring Setter Based Dependency Injection to know more about Setter DI.

You can mix both, Constructor-based and Setter-based dependency injection in Spring but it is a good rule of thumb to use constructor arguments for mandatory dependencies and setters for optional dependencies.

Recommendations for learning (Udemy Courses)

  1. Spring Framework Master Class Course
  2. Spring & Hibernate for Beginners (Includes Spring Boot)
  3. Java In-Depth: Become a Complete Java Engineer!
  4. Complete Python Bootcamp Course
  5. React - The Complete Guide Course

That's all for this topic Dependency Injection in Spring Framework. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Spring Tutorial Page


Related Topics

  1. Spring Component Scan Example
  2. Autowiring in Spring Using @Autowired and @Inject Annotations
  3. Wiring Collections in Spring
  4. Spring Example Program Using JavaConfig And Annotations
  5. How to Inject Prototype Scoped Bean in Singleton Bean

You may also like-

  1. Creating a Maven project in Eclipse
  2. Spring bean life cycle
  3. Data access in Spring framework
  4. How HashMap internally works in Java
  5. static import in Java
  6. interface static methods in Java 8
  7. Lambda expressions in Java 8
  8. Java Multi-Threading Interview Questions