Java Notes
Complete guide to Dependency Injection in Spring - constructor injection, setter injection, field injection, @Autowired, @Qualifier, circular dependencies, and best practices.
What is Dependency Injection?
Dependency Injection (DI) is a design pattern where an object receives its dependencies from an external source rather than creating them itself. In Spring, the IoC container acts as this external source.
Types of Dependency Injection
1. Constructor Injection (RECOMMENDED)
@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final EmailService emailService;
// @Autowired optional if single constructor (Spring 4.3+)
public UserService(UserRepository userRepository,
PasswordEncoder passwordEncoder,
EmailService emailService) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.emailService = emailService;
}
public User register(String name, String email, String password) {
String encoded = passwordEncoder.encode(password);
User user = new User(name, email, encoded);
userRepository.save(user);
emailService.sendWelcome(user);
return user;
}
}Why Constructor Injection is Best:
| Advantage | Explanation |
|---|---|
| Immutability | Dependencies are final — can't change after construction |
| Mandatory deps | Missing dependency = compile error |
| Testability | Easy to pass mocks via constructor |
| No reflection | Doesn't need reflection hacks |
| Fail fast | App won't start if dependency missing |
2. Setter Injection
@Service
public class NotificationService {
private EmailService emailService;
private SmsService smsService;
// Optional dependency — app works without it
@Autowired(required = false)
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
@Autowired(required = false)
public void setSmsService(SmsService smsService) {
this.smsService = smsService;
}
public void notify(User user, String message) {
if (emailService != null) {
emailService.send(user.getEmail(), message);
}
if (smsService != null) {
smsService.send(user.getPhone(), message);
}
}
}Use Setter Injection for: Optional dependencies, reconfigurable beans
3. Field Injection (AVOID in production)
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository; // Not final!
@Autowired
private CacheService cacheService; // Hard to test!
}Why Field Injection is Bad:
- Can't make fields
final(mutable) - Requires reflection (slower, fragile)
- Hard to test without Spring context
- Hides dependencies (not visible in constructor)
- Only usable in Spring-managed beans
@Autowired Deep Dive
@Service
public class SearchService {
// Inject single bean
@Autowired
private SearchEngine searchEngine;
// Inject collection of all implementations
@Autowired
private List<SearchFilter> allFilters;
// Inject map of beans (name → bean)
@Autowired
private Map<String, SearchFilter> filterMap;
// Inject Optional (Spring 5+)
@Autowired
private Optional<CacheService> cacheService;
// Inject ObjectProvider for lazy/optional access
@Autowired
private ObjectProvider<ExpensiveService> expensiveServiceProvider;
public void search(String query) {
// Use Optional
cacheService.ifPresent(cache -> cache.check(query));
// Use ObjectProvider
ExpensiveService service = expensiveServiceProvider.getIfAvailable();
}
}@Qualifier — Choosing Between Multiple Beans
// Multiple implementations of same interface
public interface NotificationSender {
void send(String to, String message);
}
@Service("emailSender")
public class EmailNotificationSender implements NotificationSender {
public void send(String to, String message) {
System.out.println("Email to " + to + ": " + message);
}
}
@Service("smsSender")
public class SmsNotificationSender implements NotificationSender {
public void send(String to, String message) {
System.out.println("SMS to " + to + ": " + message);
}
}
@Service("pushSender")
public class PushNotificationSender implements NotificationSender {
public void send(String to, String message) {
System.out.println("Push to " + to + ": " + message);
}
}
// Using @Qualifier to select
@Service
public class AlertService {
private final NotificationSender emailSender;
private final NotificationSender smsSender;
public AlertService(
@Qualifier("emailSender") NotificationSender emailSender,
@Qualifier("smsSender") NotificationSender smsSender) {
this.emailSender = emailSender;
this.smsSender = smsSender;
}
public void sendAlert(String user, String message, boolean urgent) {
emailSender.send(user, message);
if (urgent) {
smsSender.send(user, message);
}
}
}Custom Qualifier Annotations
// Define custom qualifiers
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
public @interface EmailNotification { }
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
public @interface SmsNotification { }
// Apply to implementations
@Service
@EmailNotification
public class EmailNotificationSender implements NotificationSender { }
@Service
@SmsNotification
public class SmsNotificationSender implements NotificationSender { }
// Use in injection
@Service
public class OrderService {
public OrderService(@EmailNotification NotificationSender sender) { }
}Circular Dependencies
// ❌ Circular dependency — A needs B, B needs A
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB; // ServiceB needs ServiceA!
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA; // ServiceA needs ServiceB!
}
// Spring throws: BeanCurrentlyInCreationExceptionSolutions:
// Solution 1: Use @Lazy
@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(@Lazy ServiceB serviceB) {
this.serviceB = serviceB; // Proxy injected, resolved later
}
}
// Solution 2: Setter injection (breaks cycle)
@Service
public class ServiceA {
private ServiceB serviceB;
@Autowired
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
// Solution 3: Redesign (BEST) — extract common logic
@Service
public class CommonService { } // Shared logic here
@Service
public class ServiceA {
private final CommonService commonService;
// ...
}
@Service
public class ServiceB {
private final CommonService commonService;
// ...
}DI with Java Configuration
@Configuration
public class AppConfig {
@Bean
public UserRepository userRepository() {
return new JpaUserRepository();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// Spring auto-resolves parameters from container
@Bean
public UserService userService(UserRepository repo, PasswordEncoder encoder) {
return new UserService(repo, encoder);
}
// Or call other @Bean methods directly (proxied by CGLIB)
@Bean
public AdminService adminService() {
return new AdminService(userRepository(), passwordEncoder());
// userRepository() returns SAME singleton (proxied)
}
}Testing with DI
// Unit test — inject mocks manually via constructor
class UserServiceTest {
private UserService userService;
private UserRepository mockRepo;
private PasswordEncoder mockEncoder;
@BeforeEach
void setUp() {
mockRepo = Mockito.mock(UserRepository.class);
mockEncoder = Mockito.mock(PasswordEncoder.class);
userService = new UserService(mockRepo, mockEncoder);
}
@Test
void register_shouldSaveUser() {
when(mockEncoder.encode("pass123")).thenReturn("encoded_pass");
userService.register("John", "john@test.com", "pass123");
verify(mockRepo).save(any(User.class));
}
}DI Best Practices Summary
| Do | Don't |
|---|---|
| Use constructor injection | Use field injection in production |
Make dependencies final | Allow null dependencies silently |
| Program to interfaces | Depend on concrete classes |
Use @Qualifier for multiple impls | Assume only one implementation exists |
| Keep constructors clean | Put logic in constructors |
| Prefer few dependencies (<5) | Create god-classes with 10+ deps |
Interview Key Points
- DI = container provides dependencies instead of class creating them
- Constructor injection is preferred (immutable, testable, mandatory deps)
@Autowiredis optional on single constructors since Spring 4.3@Qualifierresolves ambiguity when multiple beans of same type exist@Primarymarks the default bean when no qualifier is specified- Circular dependencies should be solved by redesign, not workarounds
- Field injection requires reflection and hides dependencies
- DI enables loose coupling and easy unit testing with mocks
Summary
In this chapter, we learned about Dependency Injection in detail. Here are the key points:
- Basic introduction to the concept and why it is needed
- Syntax and structure with complete examples
- Internal working and best practices
- Real-world applications and use cases
- Common mistakes and how to avoid them
- How this topic is asked in interviews
To practice these concepts, write and run the code in your IDE and verify the output. Modify each example and experiment so that you deeply understand the concept.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Dependency Injection.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, spring, framework, dependency
Related Java Master Course Topics