Dependency Injection Türleri
Giriş
Önceki derste IoC prensibini öğrendik: nesneler bağımlılıklarını kendileri oluşturmaz, dışarıdan alır. Peki bu "dışarıdan alma" nasıl gerçekleşir? Spring'de bağımlılıkları enjekte etmenin üç yolu vardır: Constructor Injection, Setter Injection ve Field Injection. Bu derste her birini detaylıca inceleyecek, avantaj-dezavantajlarını karşılaştıracak ve neden constructor injection'ın kesinlikle tercih edilmesi gerektiğini öğreneceğiz.
Gerçek Dünya Analojisi
Bir araba üretim bandını düşünün:
Constructor Injection = Motor, şanzıman ve direksiyon montaj sırasında takılır. Araba fabrikadan bu parçalar olmadan çıkamaz. Güvenli, eksiksiz.
Setter Injection = Araba fabrikadan çıktıktan sonra, müşteri isterse navigasyon sistemi sonradan takılabilir. Opsiyonel.
Field Injection = Parçalar arabanın içine sihirli bir şekilde beliriyor. Kimse ne zaman, nasıl takıldığını görmüyor. Tehlikeli.
Constructor Injection — Altın Standart ✅
Bağımlılıklar, sınıfın constructor'ı aracılığıyla enjekte edilir. Spring resmi olarak bu yöntemi önerir.
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
private final EmailService emailService;
// Spring 4.3+ ile tek constructor varsa @Autowired yazmak ZORUNLU DEĞİL
public OrderService(OrderRepository orderRepository,
PaymentService paymentService,
EmailService emailService) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
this.emailService = emailService;
}
public Order placeOrder(OrderRequest request) {
// Tüm bağımlılıklar kesinlikle hazır — NullPointerException imkansız
paymentService.charge(request.getAmount());
Order order = orderRepository.save(new Order(request));
emailService.sendConfirmation(order);
return order;
}
}Neden Constructor Injection Tercih Edilmeli?
1. Immutability (Değişmezlik)
private final OrderRepository orderRepository; // final = değiştirilemezfinal keyword sayesinde bağımlılık, nesne oluşturulduktan sonra asla değiştirilemez. Bu, thread-safety ve veri tutarlılığı sağlar.
2. Required Dependencies (Zorunlu Bağımlılıklar)
// Constructor'daki tüm parametreler zorunlu
// Biri eksikse → derleme hatası (IDE kırmızı çizer)
// Spring bulamazsa → uygulama başlamaz (fail-fast)3. Testability (Test Edilebilirlik)
@Test
void testPlaceOrder() {
// Mock nesneler oluştur
var mockRepo = mock(OrderRepository.class);
var mockPayment = mock(PaymentService.class);
var mockEmail = mock(EmailService.class);
// Spring container OLMADAN test et — saf Java
var service = new OrderService(mockRepo, mockPayment, mockEmail);
when(mockRepo.save(any())).thenReturn(new Order(1L));
service.placeOrder(new OrderRequest());
verify(mockPayment).charge(any());
verify(mockEmail).sendConfirmation(any());
}4. Fail-Fast Davranış Eksik bağımlılık varsa uygulama başlamaz. Hata runtime'da değil, startup'ta ortaya çıkar:
***************************
APPLICATION FAILED TO START
***************************
Parameter 0 of constructor in OrderService required a bean of type 'PaymentService' that could not be found.5. Circular Dependency Tespiti Constructor injection, döngüsel bağımlılıkları anında tespit eder:
@Service
public class ServiceA {
public ServiceA(ServiceB b) { } // B olmadan A oluşturulamaz
}
@Service
public class ServiceB {
public ServiceB(ServiceA a) { } // A olmadan B oluşturulamaz
}
// → BeanCurrentlyInCreationException — uygulama başlamaz!Bu bir uyarı değil, hediyedir. Döngüsel bağımlılık kötü tasarımın işaretidir ve erken tespit edilmesi kritiktir.
Setter Injection — Opsiyonel Bağımlılıklar İçin
Bağımlılıklar setter metotları aracılığıyla enjekte edilir. Sadece opsiyonel bağımlılıklar için kullanılmalıdır:
@Service
public class NotificationService {
private EmailSender emailSender; // Zorunlu
private SmsSender smsSender; // Opsiyonel
// Zorunlu bağımlılık → constructor ile
public NotificationService(EmailSender emailSender) {
this.emailSender = emailSender;
}
// Opsiyonel bağımlılık → setter ile
@Autowired(required = false) // false = yoksa null kalır, hata vermez
public void setSmsSender(SmsSender smsSender) {
this.smsSender = smsSender;
}
public void notify(String recipient, String message) {
emailSender.send(recipient, message); // Her zaman çalışır
// SMS servisi varsa SMS de gönder
if (smsSender != null) {
smsSender.send(recipient, message);
}
}
}Setter Injection'ın Dezavantajları
`final` kullanılamaz → bağımlılık sonradan değiştirilebilir
Mutable state → thread-safety sorunu
NullPointerException riski → opsiyonel bağımlılık null olabilir
Eksik bağımlılık → runtime'da patlar, startup'ta değil
💡 İpucu: Opsiyonel bağımlılık için setter injection yerine Optional ile constructor injection da kullanabilirsiniz:
public NotificationService(EmailSender emailSender,
Optional<SmsSender> smsSender) {
this.emailSender = emailSender;
this.smsSender = smsSender.orElse(null);
}Field Injection — Kullanmayın ❌
Bağımlılıklar doğrudan alana @Autowired ile enjekte edilir. Resmi olarak önerilmez.
@Service
public class UserService {
@Autowired // Doğrudan alana injection
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private EmailService emailService;
}Neden Field Injection Önerilmez?
1. final Kullanılamaz → Immutability Yok
@Autowired
private UserRepository userRepository; // final olamaz, değiştirilebilir2. Spring Container Olmadan Nesne Oluşturulamaz
@Test
void testBroken() {
var service = new UserService(); // new ile oluşturdum
service.findUser(1L); // 💥 NullPointerException!
// userRepository = null! @Autowired çalışmadı çünkü Spring yok
}3. Bağımlılık Sayısı Gizlenir Constructor injection'da parametre sayısı arttığında alarm çanları çalar (5+ parametre = SRP ihlali). Field injection'da 20 tane @Autowired bile fark edilmez.
4. Reflection Bağımlılığı @Autowired field injection, Java reflection ile çalışır — derleme zamanında kontrol yoktur. Constructor injection ise normal Java metot çağrısıdır.
Peki neden hâlâ kullanılıyor? Çünkü kısa yazılır. Ama bu kolaylık, tüm dezavantajlarına değmez. Lombok ile constructor injection da aynı derecede kısadır.
Lombok ile Constructor Injection — En Pratik Yol
Lombok'un @RequiredArgsConstructor annotation'ı, tüm final alanlar için otomatik constructor oluşturur:
@Service
@RequiredArgsConstructor // ← Sihir burada
public class ProductService {
private final ProductRepository productRepository; // final → constructor parametresi
private final PricingService pricingService; // final → constructor parametresi
private final InventoryService inventoryService; // final → constructor parametresi
private final NotificationService notificationService; // final → constructor parametresi
// Lombok arka planda bu constructor'ı oluşturur:
// public ProductService(ProductRepository pr, PricingService ps,
// InventoryService is, NotificationService ns) {
// this.productRepository = pr;
// this.pricingService = ps;
// this.inventoryService = is;
// this.notificationService = ns;
// }
public Product createProduct(CreateProductRequest request) {
// Tüm bağımlılıklar hazır, final ve immutable
Product product = new Product(request.getName(), request.getPrice());
product = productRepository.save(product);
inventoryService.initializeStock(product.getId(), request.getInitialStock());
notificationService.notifyNewProduct(product);
return product;
}
}Bu yaklaşım:
✅ Constructor injection'ın tüm avantajlarını sağlar
✅ Boilerplate kod sıfır
✅ Yeni bağımlılık ekleme = sadece
private finalalan eklemek✅ Production'da en yaygın pattern
⚠️ Dikkat: @RequiredArgsConstructor sadece final alanlara constructor parametresi oluşturur. final olmayan alanlar dahil edilmez.
@Autowired Detaylı
@Autowired, Spring'in dependency injection mekanizmasını tetikleyen annotation'dır:
// Constructor'da (Spring 4.3+ tek constructor'da zorunlu değil)
@Autowired // Tek constructor varsa yazmaya gerek yok
public OrderService(OrderRepository repo) { ... }
// Setter'da
@Autowired
public void setEmailService(EmailService emailService) { ... }
// Field'da
@Autowired
private EmailService emailService;
// Herhangi bir metotta
@Autowired
public void configure(DataSource dataSource, ObjectMapper mapper) {
// Bu metot Spring tarafından çağrılır ve parametreler enjekte edilir
}@Autowired ile required = false
// Bean bulunamazsa null kalır (hata vermez)
@Autowired(required = false)
private SmsSender smsSender; // SmsSender bean'i yoksa null
// Alternatif: Optional kullanın (daha modern)
@Autowired
private Optional<SmsSender> smsSender;
// smsSender.ifPresent(s -> s.send(...));Karşılaştırma Tablosu
| Özellik | Constructor ✅ | Setter ⚠️ | Field ❌ |
|---|---|---|---|
| Immutability (final) | ✅ Evet | ❌ Hayır | ❌ Hayır |
| Zorunlu bağımlılık | ✅ Garanti | ❌ Değil | ❌ Değil |
| Spring'siz test | ✅ Kolay | ⚠️ Orta | ❌ Zor |
| Opsiyonel bağımlılık | ⚠️ Optional ile | ✅ Kolay | ✅ Kolay |
| Circular dependency tespiti | ✅ Anında | ❌ Gizli | ❌ Gizli |
| Boilerplate | Orta (Lombok ile 0) | Fazla | Az |
| NullPointerException riski | ❌ Yok | ✅ Var | ✅ Var |
| Spring resmi önerisi | ✅ Önerilen | Opsiyonel için | ❌ Önerilmez |
Yaygın Hatalar ve Çözümleri
Hata 1: Field Injection ile Test Yazmaya Çalışmak
// ❌ Field injection → test zorlaşır
@Service
public class UserService {
@Autowired private UserRepository repo;
}
@Test
void test() {
UserService service = new UserService();
service.findAll(); // 💥 NullPointerException — repo null!
}
// ✅ Constructor injection → Spring'siz test
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository repo;
}
@Test
void test() {
var mockRepo = mock(UserRepository.class);
UserService service = new UserService(mockRepo); // Saf Java
when(mockRepo.findAll()).thenReturn(List.of());
// Çalışır!
}Hata 2: Çok Fazla Bağımlılık (SRP İhlali)
// ❌ 8+ bağımlılık = Bu sınıf çok fazla iş yapıyor!
@Service
@RequiredArgsConstructor
public class GodService {
private final UserRepository userRepo;
private final OrderRepository orderRepo;
private final ProductRepository productRepo;
private final EmailService emailService;
private final SmsService smsService;
private final PaymentService paymentService;
private final ShippingService shippingService;
private final ReportService reportService;
// → Single Responsibility Principle ihlali!
}
// ✅ Sorumlulukları ayırın
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepo;
private final PaymentService paymentService;
private final NotificationService notificationService; // Email + SMS'i sarmalar
// 3 bağımlılık — temiz ve odaklı
}💡 İpucu: Constructor'da 5'ten fazla parametre görüyorsanız, sınıf muhtemelen çok fazla sorumluluk üstleniyor. Bu, refactoring yapmanız gerektiğinin bir sinyalidir.
Hata 3: @Autowired'ı Constructor'da Yazmaya Devam Etmek
// Gereksiz — tek constructor'da @Autowired zorunlu değil (Spring 4.3+)
@Service
public class UserService {
private final UserRepository repo;
@Autowired // ← Gereksiz! Kaldırabilirsiniz
public UserService(UserRepository repo) {
this.repo = repo;
}
}
// Temiz versiyon
@Service
public class UserService {
private final UserRepository repo;
public UserService(UserRepository repo) { // @Autowired yok — temiz
this.repo = repo;
}
}Hata 4: Constructor + Field Injection Karışımı
// ❌ Karışık — tutarsız
@Service
public class OrderService {
private final OrderRepository repo;
@Autowired private EmailService emailService; // Field injection!
public OrderService(OrderRepository repo) {
this.repo = repo;
}
}
// ✅ Tutarlı — hepsi constructor
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository repo;
private final EmailService emailService;
}Özet
Spring'de üç DI türü vardır: Constructor (önerilen), Setter (opsiyonel için), Field (önerilmez)
Constructor injection → immutability, testability, fail-fast, circular dependency tespiti
Lombok `@RequiredArgsConstructor` +
finalalanlar = production'da altın standartSetter injection sadece gerçekten opsiyonel bağımlılıklar için
Field injection asla kullanmayın — test zorlaşır, null riski artar, bağımlılık gizlenir
5+ parametre = sınıf çok fazla iş yapıyor → refactor edin
Spring 4.3+ ile tek constructor'da `@Autowired` zorunlu değil
İleri Seviye: Birden Fazla Constructor
Sınıfta birden fazla constructor varsa, Spring hangisini kullanacağını bilemez. Bu durumda @Autowired ile hangi constructor'ın DI için kullanılacağını belirtmeniz gerekir:
@Service
public class ReportService {
private final DataSource dataSource;
private final ObjectMapper objectMapper;
private final String reportFormat;
// Bu constructor DI için kullanılır
@Autowired
public ReportService(DataSource dataSource, ObjectMapper objectMapper) {
this(dataSource, objectMapper, "PDF");
}
// Bu constructor programatik kullanım için
public ReportService(DataSource dataSource, ObjectMapper objectMapper, String reportFormat) {
this.dataSource = dataSource;
this.objectMapper = objectMapper;
this.reportFormat = reportFormat;
}
}💡 İpucu: Birden fazla constructor olduğunda @Autowired ile birini işaretlemezseniz BeanInstantiationException alırsınız. Ama pratik tavsiye: her zaman tek constructor kullanın ve Lombok @RequiredArgsConstructor ile yönetin.
Bütünleşik Gerçek Dünya Örneği
Bir kullanıcı kayıt sistemi — tüm DI kavramlarını birlikte gösterir:
// === Interface'ler ===
public interface PasswordHasher {
String hash(String plainPassword);
boolean verify(String plainPassword, String hashedPassword);
}
public interface WelcomeEmailSender {
void sendWelcome(String email, String name);
}
// === Implementasyonlar ===
@Component
public class BcryptPasswordHasher implements PasswordHasher {
private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);
@Override
public String hash(String plainPassword) {
return encoder.encode(plainPassword);
}
@Override
public boolean verify(String plainPassword, String hashedPassword) {
return encoder.matches(plainPassword, hashedPassword);
}
}
@Service
@RequiredArgsConstructor
public class SmtpWelcomeEmailSender implements WelcomeEmailSender {
private final JavaMailSender mailSender;
@Value("${app.email.from}")
private String fromAddress;
@Override
public void sendWelcome(String email, String name) {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(fromAddress);
message.setTo(email);
message.setSubject("Hoş Geldiniz, " + name + "!");
message.setText("Kayıt işleminiz başarıyla tamamlandı.");
mailSender.send(message);
}
}
// === Ana Service — Constructor Injection ===
@Service
@RequiredArgsConstructor // Tüm final alanlar constructor ile enjekte edilir
@Transactional
public class UserRegistrationService {
private final UserRepository userRepository;
private final PasswordHasher passwordHasher; // Interface bağımlılık
private final WelcomeEmailSender welcomeEmailSender; // Interface bağımlılık
public User register(RegistrationRequest request) {
// 1. Email benzersizlik kontrolü
if (userRepository.existsByEmail(request.email())) {
throw new DuplicateEmailException(request.email());
}
// 2. Şifre hash'le
String hashedPassword = passwordHasher.hash(request.password());
// 3. Kullanıcı kaydet
User user = new User(request.name(), request.email(), hashedPassword);
user = userRepository.save(user);
// 4. Hoş geldin e-postası gönder
welcomeEmailSender.sendWelcome(request.email(), request.name());
return user;
}
}
// === Test — Spring container YOK ===
class UserRegistrationServiceTest {
@Test
void shouldRegisterNewUser() {
// Arrange — Mock nesneler
var mockRepo = mock(UserRepository.class);
var mockHasher = mock(PasswordHasher.class);
var mockEmailSender = mock(WelcomeEmailSender.class);
when(mockRepo.existsByEmail(anyString())).thenReturn(false);
when(mockHasher.hash(anyString())).thenReturn("hashed_pw");
when(mockRepo.save(any())).thenAnswer(inv -> {
User u = inv.getArgument(0);
return u; // saved user
});
var service = new UserRegistrationService(mockRepo, mockHasher, mockEmailSender);
// Act
User user = service.register(new RegistrationRequest("Ali", "ali@test.com", "123456"));
// Assert
assertNotNull(user);
verify(mockHasher).hash("123456");
verify(mockEmailSender).sendWelcome("ali@test.com", "Ali");
verify(mockRepo).save(any());
}
}Bu test 0.01 saniyede çalışır — veritabanı yok, e-posta sunucusu yok, Spring context yok. Constructor injection'ın gücü budur.
AI Asistan
Sorularını yanıtlamaya hazır