Inversion of Control and XML Dependency Injection in Spring
Inversion of Control (IoC) shifts the responsibility of object creation and lifecycle management away from the objects themselves, delegating it to an external container. Dependency Injection (DI) serves as the primary mechanism to achieve this, where required components are supplied to an object rather than the object constructing them directly. This approach eliminates tight coupling caused by hard-coded instantiation.
Within the Spring ecosystem, the IoC container manages application components known as Beans. These beans can be defined using XML, annotations, or code-based configurations.
XML-Based Dependency Injection
Consider a class hierarchy where Customer extends an abstract User class.
public abstract class User {
protected String username;
}
public class Customer extends User {
private int loyaltyPoints;
private Location residentialArea;
public Customer(String username, int loyaltyPoints) {
this.username = username;
this.loyaltyPoints = loyaltyPoints;
}
public Customer(String username, int loyaltyPoints, Location residentialArea) {
this.username = username;
this.loyaltyPoints = loyaltyPoints;
this.residentialArea = residentialArea;
}
public void setLoyaltyPoints(int loyaltyPoints) {
this.loyaltyPoints = loyaltyPoints;
}
@Override
public String toString() {
return "Customer{username='" + username + "', loyaltyPoints=" + loyaltyPoints + ", location=" + residentialArea + "}";
}
}
The Location class represents a reference type dependency.
public class Location {
private String state;
private String municipality;
public Location() {}
public Location(String state, String municipality) {
this.state = state;
this.municipality = municipality;
}
public String getState() { return state; }
public void setState(String state) { this.state = state; }
public String getMunicipality() { return municipality; }
public void setMunicipality(String municipality) { this.municipality = municipality; }
@Override
public String toString() {
return "Location{state='" + state + "', municipality='" + municipality + "'}";
}
}
To bootstrap the container and retrieve beans:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Application {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("service-config.xml");
User client1 = context.getBean("client1", User.class);
System.out.println(client1);
}
}
Constructor Argument Injection
Dependencies can be injected during instantiation using the constructor-arg element.
By Parameter Name:
<bean id="client1" class="com.example.Customer">
<constructor-arg name="username" value="alice_dev" />
<constructor-arg name="loyaltyPoints" value="1200" />
</bean>
By Parameter Index:
<bean id="client2" class="com.example.Customer">
<constructor-arg index="0" value="bob_admin" />
<constructor-arg index="1" value="850" />
</bean>
Seter Method Injection
Properties can be assigned after construction using the property element. This requires the target class to provide corresponding setter methods for the fields.
<bean id="client3" class="com.example.Customer">
<constructor-arg index="0" value="charlie_ops" />
<constructor-arg index="1" value="0" />
<property name="loyaltyPoints" value="500" />
</bean>
Injecting Object References
When a dependancy is another bean, the ref attribute is used instead of value.
<bean id="hq_location" class="com.example.Location">
<property name="state" value="California" />
<property name="municipality" value="San Francisco" />
</bean>
<bean id="client4" class="com.example.Customer">
<constructor-arg name="username" value="diana_mgr" />
<constructor-arg name="loyaltyPoints" value="3400" />
<constructor-arg name="residentialArea" ref="hq_location" />
</bean>
<bean id="client5" class="com.example.Customer">
<constructor-arg index="0" value="eve_dev" />
<constructor-arg index="1" value="2100" />
<constructor-arg index="2" ref="hq_location" />
</bean>
Retrieving and verifying the reference injection:
ApplicationContext context = new ClassPathXmlApplicationContext("service-config.xml");
User client4 = context.getBean("client4", User.class);
User client5 = context.getBean("client5", User.class);
System.out.println(client4);
System.out.println(client5);
Configuring Bean Scope
By default, Spring bean are scoped as singletons. Requesting the same bean ID yields the exact same instance.
User clientA = context.getBean("client5", User.class);
User clientB = context.getBean("client5", User.class);
System.out.println(clientA == clientB); // Outputs: true
To instruct the container to produce a new instance for every request, set the scope attribute to prototype.
<bean id="client5" class="com.example.Customer" scope="prototype">
<constructor-arg index="0" value="eve_dev" />
<constructor-arg index="1" value="2100" />
<constructor-arg index="2" ref="hq_location" />
</bean>
With the prototype scope, the equality check evaluates to false:
User clientA = context.getBean("client5", User.class);
User clientB = context.getBean("client5", User.class);
System.out.println(clientA == clientB); // Outputs: false
Lazy Initialization
The ApplicationContext pre-instantiates all singleton beans during startup. This fail-fast behavior exposes configuration errors immediately. If a singleton bean should only be instantiated upon its first invocation, the lazy-init attribute can be set to true.
<bean id="lazy_client" class="com.example.Customer" lazy-init="true" scope="singleton">
<constructor-arg name="username" value="frank_test" />
<constructor-arg name="loyaltyPoints" value="100" />
</bean>
Verifying the deferred instantiation:
public class Application {
public static void main(String[] args) throws InterruptedException {
ApplicationContext context = new ClassPathXmlApplicationContext("service-config.xml");
System.out.println("Container initialized.");
Thread.sleep(2000);
User lazyClient = context.getBean("lazy_client", User.class);
System.out.println("Lazy bean retrieved.");
}
}