1. What is a Dependency?
A dependency exists when one class needs another class to perform its work.
class EmailService {
public void sendEmail(String message) {
System.out.println("Sending: " + message);
}
}
class OrderService {
private EmailService emailService;
public void placeOrder() {
emailService.sendEmail("Order placed");
}
}
Relationship:
OrderService ---> EmailService
OrderService depends on EmailService because it needs EmailService to perform its email-related functionality.
2. The Problem: Who Creates the Dependency?
A simple implementation may create the dependency inside the class.
class OrderService {
private EmailService emailService = new EmailService();
public void placeOrder() {
emailService.sendEmail("Order placed");
}
}
Now OrderService is responsible for creating and using EmailService.
OrderService
|
| creates
v
EmailService
If the implementation changes from EmailService to SMS or another notification mechanism, OrderService must also change.
3. What is Tight Coupling?
When a class directly creates and depends on a concrete implementation, the classes become tightly coupled.
class OrderService {
private EmailService emailService =
new EmailService();
public void placeOrder() {
emailService.sendEmail("Order placed");
}
}
OrderService is tightly coupled to EmailService.
Problem: If we replace EmailService with GmailService, SmsService or another implementation, OrderService has to be modified.
A common design principle is:
Program to an interface, not an implementation.
4. Better Design Using an Interface
public interface NotificationService {
void sendNotification(String message);
}
Email Implementation
class EmailNotificationService
implements NotificationService {
@Override
public void sendNotification(String message) {
System.out.println(
"Sending Email: " + message);
}
}
SMS Implementation
class SmsNotificationService
implements NotificationService {
@Override
public void sendNotification(String message) {
System.out.println(
"Sending SMS: " + message);
}
}
Now the client can depend on the interface rather than a concrete implementation.
class OrderService {
private NotificationService notificationService;
public void placeOrder() {
notificationService.sendNotification(
"Order placed");
}
}
The remaining question is: Who provides NotificationService to OrderService?
This leads us to Dependency Injection.
5. Different Approaches for Managing Dependencies
Ways of obtaining/managing a dependency
├── 1. Composition
├── 2. Factory Pattern
├── 3. JNDI Registry
├── 4. Inheritance
└── 5. IoC
├── Dependency Lookup / Pull
└── Dependency Injection / Push
├── Constructor Injection
├── Setter Injection
└── Field Injection
6. Composition
Composition means one class contains or uses another object as a member.
class Engine {
public void start() {
System.out.println("Engine started");
}
}
class Car {
private Engine engine;
public Car() {
this.engine = new Engine();
}
public void drive() {
engine.start();
System.out.println("Car is running");
}
}
Car
|
| contains
v
Engine
This represents a HAS-A relationship.
Composition with Dependency Injection
class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void drive() {
engine.start();
System.out.println("Car is running");
}
}
Engine engine = new Engine();
Car car = new Car(engine);
Now Car does not create Engine. The dependency is provided from outside.
7. Factory Pattern
A Factory centralizes object creation logic.
interface NotificationService {
void send(String message);
}
class EmailNotificationService
implements NotificationService {
@Override
public void send(String message) {
System.out.println("Email: " + message);
}
}
class SmsNotificationService
implements NotificationService {
@Override
public void send(String message) {
System.out.println("SMS: " + message);
}
}
Factory
class NotificationFactory {
public static NotificationService
getService(String type) {
if ("EMAIL".equalsIgnoreCase(type)) {
return new EmailNotificationService();
}
if ("SMS".equalsIgnoreCase(type)) {
return new SmsNotificationService();
}
throw new IllegalArgumentException(
"Unknown notification type");
}
}
Client
class OrderService {
private NotificationService notificationService;
public OrderService(String type) {
this.notificationService =
NotificationFactory.getService(type);
}
public void placeOrder() {
notificationService.send("Order placed");
}
}
OrderService
|
| asks
v
NotificationFactory
|
+----> EmailNotificationService
|
+----> SmsNotificationService
The Factory hides object creation logic, but the client still knows about the Factory.
8. JNDI Registry
JNDI stands for Java Naming and Directory Interface.
It allows an application to look up resources from a naming registry.
Application
|
| lookup("resource-name")
v
JNDI Registry
|
v
Resource
Example
Context context = new InitialContext();
DataSource dataSource =
(DataSource) context.lookup(
"java:/comp/env/jdbc/MyDB");
The application asks the registry for a resource registered under a particular name.
This is an example of Dependency Lookup.
9. Dependency Lookup / Pull Model
In Dependency Lookup, the object itself asks a container or registry for its dependency.
class OrderService {
public void process() {
NotificationService service =
Container.getNotificationService();
service.send("Order placed");
}
}
OrderService
|
| "Give me NotificationService"
v
Container
This is called the Pull Model because the object pulls the dependency.
Key point: The class generally knows about the container or lookup mechanism.
10. Dependency Injection / Push Model
In Dependency Injection, the class does not ask for its dependency. The dependency is provided to the class.
class OrderService {
private NotificationService notificationService;
public OrderService(
NotificationService notificationService) {
this.notificationService =
notificationService;
}
public void placeOrder() {
notificationService.send("Order placed");
}
}
NotificationService notificationService =
new EmailNotificationService();
OrderService orderService =
new OrderService(notificationService);
NotificationService
|
| injected
v
OrderService
The dependency is pushed into OrderService.
11. What is IoC?
IoC means Inversion of Control.
Normally, application code controls object creation:
OrderService service = new OrderService();
EmailService emailService =
new EmailService();
Your code decides:
When to create
What to create
How to create
Which implementation to use
With Spring, control is transferred to the Spring IoC Container.
You
|
| Configure application components
v
Spring IoC Container
|
| creates objects
| manages objects
| injects dependencies
v
Application
IoC means that control over object creation and dependency management is transferred from your application code to the Spring container.
12. Spring IoC Container
The central concept in Spring Core is the IoC Container.
BeanFactory
|
v
ApplicationContext
ApplicationContext is the commonly used container in Spring applications.
It manages Spring Beans.
A Spring Bean is simply an object that is created and managed by Spring.
@Component
public class EmailService {
public void sendEmail(String message) {
System.out.println(
"Email sent: " + message);
}
}
Spring detects the class and creates an object managed by the container.
13. Basic Spring IoC Example
Step 1: Interface
public interface NotificationService {
void send(String message);
}
Step 2: Implementation
import org.springframework.stereotype.Component;
@Component
public class EmailNotificationService
implements NotificationService {
@Override
public void send(String message) {
System.out.println(
"Email sent: " + message);
}
}
@Component tells Spring to create and manage an object of this class.
Step 3: Client Class
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final NotificationService notificationService;
public OrderService(
NotificationService notificationService) {
this.notificationService =
notificationService;
}
public void placeOrder() {
System.out.println("Order placed");
notificationService.send(
"Order confirmation");
}
}
Notice: We did not use new EmailNotificationService() inside OrderService. Spring provides the dependency.
14. What Happens During Spring Boot Startup?
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(
Application.class,
args);
}
}
Conceptually, Spring Boot performs these activities:
SpringApplication.run()
|
v
Create ApplicationContext
|
v
Scan classes
|
v
Find @Component / @Service / @Repository / @Controller
|
v
Create Bean Definitions
|
v
Create Bean Objects
|
v
Resolve Dependencies
|
v
Perform Dependency Injection
|
v
Initialize Beans
|
v
Application Ready
15. Constructor Injection
Constructor Injection is generally the preferred form of dependency injection for required dependencies.
@Service
public class OrderService {
private final NotificationService notificationService;
public OrderService(
NotificationService notificationService) {
this.notificationService =
notificationService;
}
}
With a single constructor, modern Spring can automatically use it without @Autowired.
You can also explicitly write:
@Autowired
public OrderService(
NotificationService notificationService) {
this.notificationService =
notificationService;
}
Why Constructor Injection?
- Required dependencies are available during construction.
- Dependencies are clearly visible.
- Fields can be final.
- Easy to unit test.
- Reduces the possibility of creating an invalid object.
16. Setter Injection
In Setter Injection, Spring creates the object and then calls a setter method to provide the dependency.
@Service
public class OrderService {
private NotificationService notificationService;
@Autowired
public void setNotificationService(
NotificationService notificationService) {
this.notificationService =
notificationService;
}
}
1. Spring creates OrderService
|
v
2. Spring calls setNotificationService()
|
v
3. Dependency is injected
When is Setter Injection useful?
- Optional dependencies
- Configurable dependencies
- Dependencies that do not need to exist during construction
17. Field Injection
@Service
public class OrderService {
@Autowired
private NotificationService notificationService;
public void placeOrder() {
notificationService.send(
"Order placed");
}
}
Spring injects the dependency directly into the field, generally using reflection.
Advantages
- Very little code.
- Easy to write for simple examples.
Disadvantages
- Dependencies are hidden.
- Fields are generally not final.
- Unit testing without Spring is less convenient.
- It can encourage classes with too many hidden dependencies.
Recommendation: Prefer constructor injection for required dependencies in new application code.
18. Constructor vs Setter vs Field Injection
| Feature |
Constructor |
Setter |
Field |
| Required dependency |
Excellent |
Less suitable |
Less suitable |
| Optional dependency |
Possible |
Good |
Possible |
| Immutable field |
Yes |
No |
Usually no |
| Dependencies clearly visible |
Yes |
Moderately |
No |
| Unit testing |
Easy |
Easy |
Less convenient |
| General recommendation |
Preferred |
Sometimes |
Generally avoid in new code |
Required dependency
↓
Constructor Injection
Optional/configurable dependency
↓
Setter Injection
Field Injection
↓
Usually avoid in new code
19. Why Interfaces Are Important in DI
public interface NotificationService {
void send(String message);
}
Possible implementations:
EmailNotificationService
SmsNotificationService
PushNotificationService
All implementations follow the same abstraction:
NotificationService
Therefore, OrderService can depend on the interface:
public OrderService(
NotificationService notificationService) {
this.notificationService =
notificationService;
}
OrderService does not need to know which concrete implementation is used. This promotes loose coupling.
20. Multiple Implementations and @Qualifier
Suppose there are two implementations:
@Component("emailService")
public class EmailNotificationService
implements NotificationService {
}
@Component("smsService")
public class SmsNotificationService
implements NotificationService {
}
If we write:
public OrderService(
NotificationService notificationService) {
}
Spring may not know which implementation should be injected.
Use @Qualifier:
@Service
public class OrderService {
private final NotificationService notificationService;
public OrderService(
@Qualifier("emailService")
NotificationService notificationService) {
this.notificationService =
notificationService;
}
}
OrderService
|
| @Qualifier("emailService")
v
EmailNotificationService
21. @Primary
If multiple implementations exist, one implementation can be marked as the default using @Primary.
@Component
@Primary
public class EmailNotificationService
implements NotificationService {
}
Then Spring can inject it when the dependency type is:
NotificationService
without requiring a qualifier in every injection point.
22. What is a Spring Bean?
A Spring Bean is an object created, configured, and managed by the Spring IoC container.
@Component
public class EmailService {
}
Other common stereotype annotations include:
| Annotation | Typical Purpose |
@Component | General Spring-managed component |
@Service | Business/service layer |
@Repository | Persistence/data-access layer |
@Controller | Web/MVC controller |
@RestController | REST API controller |
@Component
|
+---- @Service
|
+---- @Repository
|
+---- @Controller
23. Using @Bean
Instead of annotating a class with @Component, we can explicitly configure a bean using @Bean.
@Configuration
public class AppConfig {
@Bean
public EmailService emailService() {
return new EmailService();
}
}
This is particularly useful for configuring third-party classes that you cannot modify to add @Component.
Example
@Configuration
public class AppConfig {
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
Spring can then inject ObjectMapper wherever it is required.
24. @Component vs @Bean
| @Component | @Bean |
| Placed on the class |
Placed on a method |
| Usually discovered through component scanning |
Explicitly registered through configuration |
| Good for classes you control |
Useful for third-party classes |
| Example: business/service classes |
Example: ObjectMapper, custom clients |
25. Composition vs Inheritance
Inheritance
class Vehicle {
public void start() {
}
}
class Car extends Vehicle {
}
Relationship:
Car IS-A Vehicle
Composition
class Car {
private Engine engine;
}
Relationship:
Car HAS-A Engine
Inheritance
↓
IS-A
Composition
↓
HAS-A
For many application-design situations, composition is preferred when the relationship is genuinely "has-a", because it avoids unnecessary inheritance coupling.
26. Real-World Example: Payment System
PaymentGateway Interface
public interface PaymentGateway {
void pay(double amount);
}
Implementation
@Component
public class StripePaymentGateway
implements PaymentGateway {
@Override
public void pay(double amount) {
System.out.println(
"Payment using Stripe: " + amount);
}
}
PaymentService
@Service
public class PaymentService {
private final PaymentGateway paymentGateway;
public PaymentService(
PaymentGateway paymentGateway) {
this.paymentGateway = paymentGateway;
}
public void processPayment(double amount) {
paymentGateway.pay(amount);
}
}
OrderService
@Service
public class OrderService {
private final PaymentService paymentService;
public OrderService(
PaymentService paymentService) {
this.paymentService = paymentService;
}
public void placeOrder(double amount) {
paymentService.processPayment(amount);
System.out.println(
"Order placed successfully");
}
}
Dependency Graph
Spring IoC Container
|
v
OrderService
|
v
PaymentService
|
v
PaymentGateway
|
v
StripePaymentGateway
None of these classes needs to manually create its dependency using new.
27. Dependency Injection and Unit Testing
One of the biggest advantages of DI is testability.
@Service
public class PaymentService {
private final PaymentGateway paymentGateway;
public PaymentService(
PaymentGateway paymentGateway) {
this.paymentGateway = paymentGateway;
}
public void processPayment(double amount) {
paymentGateway.pay(amount);
}
}
Production:
PaymentService
|
v
StripePaymentGateway
Testing:
PaymentService
|
v
MockPaymentGateway
Example:
PaymentGateway mockGateway =
Mockito.mock(PaymentGateway.class);
PaymentService service =
new PaymentService(mockGateway);
The test can execute PaymentService without contacting a real payment provider.
28. IoC vs DI
| IoC | DI |
| Inversion of Control is a broader design principle. |
Dependency Injection is a technique/mechanism. |
| Control of object creation and dependency management is transferred to a container/framework. |
Dependencies are supplied to an object instead of the object creating or looking them up. |
| Describes the overall change in control. |
Describes how dependencies are provided. |
Remember: DI is one of the primary techniques used to implement IoC.
29. Pull vs Push Model
Pull — Dependency Lookup
class OrderService {
public void process() {
NotificationService service =
container.getBean(
NotificationService.class);
service.send("Order placed");
}
}
Object ---> pulls ---> Dependency
Push — Dependency Injection
class OrderService {
private final NotificationService service;
public OrderService(
NotificationService service) {
this.service = service;
}
}
Container ---> pushes ---> Dependency ---> Object
30. Typical Spring Boot Dependency Graph
Controller
|
v
OrderService
|
+----------------+
| |
v v
PaymentService OrderRepository
|
v
PaymentGateway
Spring manages these objects and resolves the dependencies between them.
For example:
@RestController
public class OrderController {
private final OrderService orderService;
public OrderController(
OrderService orderService) {
this.orderService = orderService;
}
}
@Service
public class OrderService {
private final PaymentService paymentService;
private final OrderRepository orderRepository;
public OrderService(
PaymentService paymentService,
OrderRepository orderRepository) {
this.paymentService = paymentService;
this.orderRepository = orderRepository;
}
}
31. Important Interview Answers
What is IoC?
IoC, or Inversion of Control, is a design principle in which the responsibility for creating, configuring, managing, and wiring objects is transferred from application code to a container or framework. In Spring, the IoC container manages Spring beans and their dependencies.
What is Dependency Injection?
Dependency Injection is a technique used to implement IoC. Instead of a class creating or looking up its dependencies, the dependencies are provided to the class by an external container. Spring supports constructor, setter, and field injection, with constructor injection generally preferred for required dependencies.
Dependency Lookup vs Dependency Injection?
In Dependency Lookup, the object actively asks a container or registry for its dependency, so it follows a pull model. In Dependency Injection, the dependency is supplied to the object from outside, so it follows a push model. Spring primarily promotes Dependency Injection because it reduces coupling between application classes and the container.
Why is Constructor Injection preferred?
- It makes required dependencies explicit.
- It supports immutable fields.
- It makes objects easier to construct correctly.
- It simplifies unit testing.
- It avoids hidden dependencies.
32. Final Summary
DEPENDENCY
|
v
How do I obtain my dependency?
|
+----------+----------+
| | |
v v v
Composition Factory JNDI
| | |
+----------+----------+
|
v
IoC
|
+---------+---------+
| |
v v
Dependency Lookup Dependency Injection
(Pull) (Push)
|
+---------------+---------------+
| | |
v v v
Constructor Setter Field
Injection Injection Injection
The Most Important Comparison
Without IoC/DI
class OrderService {
PaymentService paymentService =
new PaymentService();
}
The class creates its dependency.
With IoC/DI
class OrderService {
private final PaymentService paymentService;
OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
The dependency is provided to the class.
One-Sentence Definition
IoC means Spring takes control of object creation and dependency management; Dependency Injection is the mechanism through which Spring supplies those dependencies to your objects.
Easy Memory Trick
| Concept | Easy Meaning |
| Dependency | One class needs another class |
| Composition | HAS-A relationship |
| Inheritance | IS-A relationship |
| Factory | Object creation is centralized |
| JNDI | Look up a resource from a registry |
| Dependency Lookup | Object pulls the dependency |
| Dependency Injection | Dependency is pushed into the object |
| IoC | Framework/container controls object creation and management |
| Constructor Injection | Required dependency provided through constructor |
| Setter Injection | Dependency provided through setter |
| Field Injection | Dependency injected directly into a field |
| Spring Bean | Object managed by Spring IoC container |