Dependency Injection in Spring Framework
Basically Spring is a framework for Dependency Injection (DI) which is a pattern that allows to build very decoupled systems. DI is part of Inversion of Control (IoC) principle, is the process of providing the dependencies.
The core of the Spring Framework is Ioc container. The IoC container manages java objects from instantiation to destruction. And the IoC container manages bean scope, lifecycle, and all AOP features for which it has been configured and coded.
The problem
For example, suppose you need to list the customers in the system and thus declare an interface called CustomerList:
package com.dariawan.codes.spring;
import java.util.List;
public interface CustomerList {
List<Customer> getCustomers();
}
And when we need to access database, maybe we will have an implementation to query DB to get all the customers:
package com.dariawan.codes.spring;
import java.util.ArrayList;
import java.util.List;
public class CustomerListDB implements CustomerList {
@Override
public List<Customer> getCustomers() {
// method implementation of DB access code
return new ArrayList<>(); // this is just example
}
}
Now, what if I need to print all this customers? Maybe we will have codes like this:
package com.dariawan.codes.spring;
import java.util.List;
public class CustomerPrint {
private CustomerList customerList;
/**
* @param customerList the customerList to set
*/
public void setCustomerList(CustomerList customerList) {
this.customerList = customerList;
}
public void print() {
// This is just example
List<Customer> customers = customerList.getCustomers();
for (Customer customer : customers) {
System.out.println(customer);
}
}
}
Take note that the code above doesn't have initialized the variable customerList. So, how we should do this? Explicitely initialize like this?
CustomerList customerList = new CustomerListDB();
What if we have another source. Let say CSV file that contains customer information (not practical, but hey... this is only example)
package com.dariawan.codes.spring;
import java.util.ArrayList;
import java.util.List;
public class CustomerListCSV implements CustomerList {
@Override
public List<Customer> getCustomers() {
// method implementation to read customers data from CSV here
return new ArrayList<>(); // this is just example
}
}
Now, I want to switch from the DB implementation to this CSV reading implementation. No worries, I already do it in interface manner. What I need to do is to change the initialization part:
CustomerList customerList = new CustomerListCSV();
This has no problem with a small program like this but... in real world these kind of programs only exists in school projects and tutorial examples (like this one). In reality, the enterprise software (that's why we choose Java and Spring) is more complex. They will have hundreds or thousands of classes (plus interfaces), and nested references. Maintaining codes with this conservative approach will becomes a nightmare for the programmer!
Spring Dependency Injection (DI)
What Spring does is to wire the classes up by using an XML file or annotations, this way all the objects are instantiated and initialized by Spring and injected in the right places (DAOs, services, servlets, you name it...).
Going back to the example in Spring we just need to have a setter for the customerList field and have either an XML file like this:
<?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.xsd">
<bean id="customerList" class="com.dariawan.codes.spring.CustomerListDB" />
<bean id="customerPrint" class="com.dariawan.codes.spring.CustomerPrint">
<property name="customerList" ref="customerList" />
</bean>
</beans>
This way when the CustomerPrint is created it magically will have a CustomerList ready to work.
// This will actually work without adding any line of code List<Customer> customers = customerList.getCustomers();
Let's play more with our codes to check if this magical things really happen... I'll have a concrete Customer class:
package com.dariawan.codes.spring;
public class Customer {
private String source;
private String name;
/**
* @return the source
*/
public String getSource() {
return source;
}
/**
* @param source the source to set
*/
public void setSource(String source) {
this.source = source;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
public Customer(String source, String name) {
this.source = source;
this.name = name;
}
@Override
public String toString() {
return "Customer{" + "source=" + source + ", name=" + name + '}';
}
}
And change CustomerListDB and CustomerListCSV to include some sample data (this is not for PRODuction, remember...). No change in CustomerPrint class.
package com.dariawan.codes.spring;
import java.util.ArrayList;
import java.util.List;
public class CustomerListDB implements CustomerList {
@Override
public List<Customer> getCustomers() {
// sample data...
List<Customer> customers = new ArrayList<>();
customers.add(new Customer("DB", "Barbara"));
customers.add(new Customer("DB", "Cassandra"));
customers.add(new Customer("DB", "Daryna"));
return customers;
}
}
package com.dariawan.codes.spring;
import java.util.ArrayList;
import java.util.List;
public class CustomerListCSV implements CustomerList {
@Override
public List<Customer> getCustomers() {
// sample data...
List<Customer> customers = new ArrayList<>();
customers.add(new Customer("CSV", "Bianca"));
customers.add(new Customer("CSV", "Celena"));
customers.add(new Customer("CSV", "Elyssa"));
return customers;
}
}
And let's create CustomerListApp to run our example
package com.dariawan.codes.spring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class CustomerListApp {
public static void main(String[] args) {
ApplicationContext appContext = new ClassPathXmlApplicationContext("beans-customer-list.xml");
CustomerPrint customerPrint = appContext.getBean("customerPrint", CustomerPrint.class);
customerPrint.print();
}
}
and the result we get:
Customer{source=DB, name=Barbara} Customer{source=DB, name=Cassandra} Customer{source=DB, name=Daryna}
Now in beans-customer-list.xml, we change bean customerList's class to CustomerListCSV
<bean id="customerList" class="com.dariawan.codes.spring.CustomerListCSV" />
And when we run CustomerListApp again... voila, we get different result:
Customer{source=CSV, name=Bianca} Customer{source=CSV, name=Celena} Customer{source=CSV, name=Elyssa}
Magic does exists...
Summary
Now, from above example you know how to use Spring DI for your advantages:
- What if we want to use another implementation of CustomerList interface? Just edit the XML.
- What if don't have a CustomerList implementation ready? This is just happen, programmer just prepare a placeholder for their 'creative-high-design'. It's also happen many times in my project, no worries. Just create a the default implementation of CustomerList and continue. The more concrete implementation can come later.
- What if I don't want to use Spring anymore? Just don't use it! Your application isn't coupled to it.
Besides via XML configuration, you also can declare your configuration via annotation and Java code.