Dependency Injection (DI) is one of the core concepts of the Spring Framework, and it helps achieve Inversion of Control (IoC), making your code more modular, testable, and easier to maintain. DI is a technique in which one object (or method) supplies the dependencies (objects or services) to another object. Spring provides a comprehensive DI mechanism that can be used in a variety of scenarios.
Let’s break down Dependency Injection in the Spring Framework, covering key concepts, types, and examples.
1. What is Dependency Injection (DI)?
Dependency Injection is a design pattern that deals with how objects (beans) get their dependencies. Instead of creating the dependencies (like services, repositories, etc.) within the class, the dependencies are injected from outside, typically via the Spring container.
Key Benefits of DI in Spring:
Loose Coupling: Classes are not directly responsible for instantiating their dependencies.
Easier Unit Testing: Dependencies can be easily mocked or stubbed for unit tests.
Separation of Concerns: Classes don’t have to manage the lifecycle and configuration of their dependencies.
2. Types of Dependency Injection in Spring
There are three main types of Dependency Injection in Spring:
1. Constructor-based Injection:
The dependencies are provided through the class constructor. This is the most preferred method because it makes it easier to ensure that the dependencies are immutable and mandatory.
@Component
public class Car {
private Engine engine;
// Constructor-based Dependency Injection
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.run();
}
}
Here, the Engine dependency is passed through the constructor, and Spring will automatically provide the correct Engine bean when it creates a Car bean.
2. Setter-based Injection:
Dependencies are provided via setter methods. This is more flexible as it allows the dependencies to be set after object creation. However, it's less explicit and can make some dependencies optional.
@Component
public class Car {
private Engine engine;
// Setter-based Dependency Injection
@Autowired
public void setEngine(Engine engine) {
this.engine = engine;
}
public void start() {
engine.run();
}
}
In this example, Spring will call the setEngine method to inject the Engine dependency.
3. Field-based Injection (Not Recommended for Large Applications):
In this case, dependencies are injected directly into the fields using the @Autowired annotation. This is the easiest way to inject dependencies but can hide dependencies, making the class less transparent and harder to test.
@Component
public class Car {
@Autowired
private Engine engine; // Field-based Dependency Injection
public void start() {
engine.run();
}
}
Note: This approach is not recommended in larger applications because it makes it harder to see the required dependencies of a class, and it can lead to issues with immutability and testability.
3. Spring DI with Annotations
Spring offers annotations to handle DI easily. Here are some of the most commonly used annotations in Spring for DI:
1. @Autowired (Used for Dependency Injection)
The @Autowired annotation is used to inject the dependencies either via constructor, setter, or field.
Constructor-based injection: If a class has only one constructor, Spring automatically uses it to inject the dependencies.
Setter-based injection: Spring will call the setter methods to inject the dependencies.
Field-based injection: Spring injects the dependencies directly into the fields of the class.
2. @Component/@Service/@Repository (Marks Beans for DI)
Spring uses these annotations to mark classes as beans that Spring should manage.
@Component is a generic annotation for Spring beans.
@Service is used for service layer beans.
@Repository is used for DAO or persistence layer beans.
@Controller is used for Spring MVC controllers.
@Component
public class Engine {
public void run() {
System.out.println("Engine is running...");
}
}
3. @Configuration and @Bean (Java-based Configuration)
Instead of XML configuration, Spring also supports Java-based configuration using the @Configuration annotation.
@Configuration
public class AppConfig {
@Bean
public Car car() {
return new Car(engine());
}
@Bean
public Engine engine() {
return new Engine();
}
}
This approach is explicit and is usually preferred over XML configuration, especially when dealing with complex configurations.
4. Spring Bean Lifecycle and Dependency Injection
Spring manages the lifecycle of beans and handles the initialization and destruction of beans automatically. DI helps Spring to instantiate the objects and inject dependencies based on the defined configurations.
How Spring Creates Beans:
Instantiation: Spring uses the default constructor or a constructor marked with @Autowired.
Dependency Injection: Spring injects the necessary dependencies into the bean using constructor, setter, or field injection.
Post-Processing: After bean creation and dependency injection, @PostConstruct and @PreDestroy annotations can be used for any post-processing or cleanup.
@PostConstruct Example:
@Component
public class Car {
private Engine engine;
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
@PostConstruct
public void init() {
System.out.println("Car is ready to go!");
}
}
5. Autowiring in Spring
Autowiring by Type (@Autowired)
Spring can inject beans based on the type of the dependency.
@Autowired
private Engine engine;
Autowiring by Name (Using @Qualifier)
If there are multiple beans of the same type, you can use the @Qualifier annotation to specify which bean to inject.
@Autowired
@Qualifier("dieselEngine")
private Engine engine;
Autowiring by Constructor
You can also use constructor-based injection, where Spring automatically wires dependencies via the constructor:
@Component
public class Car {
private Engine engine;
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
}
6. Scope of Beans
Spring beans can be configured with different scopes, which define the lifespan of the beans:
Singleton (default): Only one instance of the bean is created, shared throughout the Spring context.
Prototype: A new instance is created each time the bean is requested.
Request: A bean is created for every HTTP request.
Session: A bean is created for each HTTP session.
@Component
@Scope("prototype")
public class Engine {
// Bean will be created every time it is requested
}
7. Dependency Injection with Spring XML Configuration
Although annotations are most commonly used in modern Spring applications, it is also possible to define DI using Spring's XML configuration.
<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.3.xsd">
<!-- Define Beans -->
<bean id="engine" class="com.example.Engine"/>
<bean id="car" class="com.example.Car">
<constructor-arg ref="engine"/>
</bean>
</beans>
This configuration injects the Engine bean into the Car bean through the constructor.
8. Conclusion
Dependency Injection in Spring is essential for building loosely coupled, maintainable, and testable applications. Spring's DI supports different types of injection (constructor, setter, and field) and provides multiple ways to wire beans, either through annotations, XML configuration, or Java-based configuration.
Advantages of Spring's DI:
Loose Coupling: Spring handles object creation and wiring of dependencies.
Testability: Easier to write unit tests by mocking dependencies.
Flexibility: Spring can handle complex object graphs and lifecycle management.
Separation of Concerns: Different layers (controllers, services, repositories) can be easily separated.
Learn Full Stack JAVA Course in Hyderabad
Read More
Creating REST APIs using Spring Boot
Spring Boot vs Spring MVC – Key Differences
What is Spring Framework? An Overview
Java Backend Architecture – MVC Explained
Visit Our Quality Thought Institute in Hyderabad
Subscribe by Email
Follow Updates Articles from This Blog via Email
No Comments