Dlaczego autowiring zmienia sposób pisania kodu?
Bez autowiring musiałbyś ręcznie tworzyć wszystkie obiekty i zarządzać ich zależnościami – to prowadzi do mocno powiązanego kodu. Spring autowiring automatyzuje ten proces, pozwalając skupić się na logice biznesowej zamiast na plumbing code. To jak różnica między montowaniem samochodu ręcznie a otrzymaniem gotowego pojazdu z fabryki.
Co się nauczysz:
- Jak działa mechanizm autowiring w Spring Framework
- Różne sposoby wstrzykiwania zależności (@Autowired locations)
- Typy autowiring: byType, byName, constructor
- Rozwiązywanie problemów z autowiring (multiple beans, optional dependencies)
- Best practices dla dependency injection
Czym jest autowiring w Spring?
Autowiring to mechanizm Spring Framework który automatycznie wstrzykuje zależności do obiektów (beanów) zarządzanych przez Spring container. Zamiast ręcznie tworzyć obiekty, Spring robi to za ciebie na podstawie konfiguracji.
Przykład bez autowiring (tradycyjny sposób):
// Ręczne tworzenie zależności - tight coupling public class OrderService { private PaymentService paymentService; private EmailService emailService; public OrderService() { // Ręczne tworzenie - złe podejście! this.paymentService = new CreditCardPaymentService(); this.emailService = new SmtpEmailService(); } public void processOrder(Order order) { paymentService.processPayment(order.getAmount()); emailService.sendConfirmation(order.getCustomerEmail()); } }
Ten sam kod z Spring autowiring:
@Service public class OrderService { @Autowired private PaymentService paymentService; @Autowired private EmailService emailService; // Konstruktor pusty - Spring zadba o wstrzyknięcie zależności public void processOrder(Order order) { paymentService.processPayment(order.getAmount()); emailService.sendConfirmation(order.getCustomerEmail()); } }
Sposoby autowiring – gdzie umieścić @Autowired
1. Field Injection (wstrzykiwanie do pól)
@Service public class UserService { @Autowired private UserRepository userRepository; // Bezpośrednio do pola @Autowired private EmailService emailService; public User createUser(String email) { User user = new User(email); userRepository.save(user); emailService.sendWelcomeEmail(email); return user; } }
2. Setter Injection (wstrzykiwanie przez settery)
@Service public class ProductService { private ProductRepository productRepository; private PriceCalculator priceCalculator; @Autowired public void setProductRepository(ProductRepository productRepository) { this.productRepository = productRepository; } @Autowired public void setPriceCalculator(PriceCalculator priceCalculator) { this.priceCalculator = priceCalculator; } public Product createProduct(String name, double basePrice) { Product product = new Product(name); product.setPrice(priceCalculator.calculate(basePrice)); return productRepository.save(product); } }
3. Constructor Injection (wstrzykiwanie przez konstruktor) – RECOMMENDED
@Service public class OrderService { private final PaymentService paymentService; private final EmailService emailService; // @Autowired opcjonalne dla pojedynczego konstruktora public OrderService(PaymentService paymentService, EmailService emailService) { this.paymentService = paymentService; this.emailService = emailService; } public void processOrder(Order order) { paymentService.processPayment(order.getAmount()); emailService.sendConfirmation(order.getCustomerEmail()); } }
Typy autowiring – jak Spring znajduje beany
1. By Type (domyślnie)
Spring szuka beana o pasującym typie:
@Service public class NotificationService { @Autowired private EmailService emailService; // Spring znajdzie bean typu EmailService } @Component public class SmtpEmailService implements EmailService { // Implementacja EmailService - Spring użyje tego beana }
2. By Name (gdy jest konflikt typów)
// Dwie implementacje tego samego interfejsu @Component public class SmtpEmailService implements EmailService { } @Component public class SendGridEmailService implements EmailService { } @Service public class NotificationService { @Autowired private EmailService smtpEmailService; // Spring znajdzie bean o nazwie "smtpEmailService" }
3. Używanie @Qualifier dla precyzyjnego wyboru
@Component @Qualifier("smtp") public class SmtpEmailService implements EmailService { } @Component @Qualifier("sendgrid") public class SendGridEmailService implements EmailService { } @Service public class NotificationService { @Autowired @Qualifier("smtp") private EmailService emailService; // Wybiera konkretną implementację }
Rozwiązywanie problemów z autowiring
Problem: NoSuchBeanDefinitionException
Rozwiązania:
- Sprawdź czy klasa ma adnotację @Component/@Service/@Repository
- Upewnij się że pakiet jest skanowany przez @ComponentScan
- Sprawdź czy używasz właściwego typu (interface vs implementacja)
Problem: Multiple beans tego samego typu
// Rozwiązanie 1: @Primary @Component @Primary public class SmtpEmailService implements EmailService { } // Rozwiązanie 2: @Qualifier @Autowired @Qualifier("sendgrid") private EmailService emailService; // Rozwiązanie 3: Injection wszystkich implementacji @Autowired private ListemailServices; // Spring wstrzyknie wszystkie
Optional dependencies – @Autowired(required = false)
@Service public class UserService { @Autowired(required = false) private CacheService cacheService; // Może być null jeśli bean nie istnieje @Autowired private OptionalmetricsService; // Java 8 Optional public User getUser(Long id) { if (cacheService != null) { User cached = cacheService.get(id); if (cached != null) return cached; } User user = userRepository.findById(id); metricsService.ifPresent(service -> service.recordUserAccess(id)); return user; } }
Best practices dla autowiring
1. Preferuj Constructor Injection
Typ | Zalety | Wady | Kiedy używać |
---|---|---|---|
Constructor | Immutable, testable, fail-fast | Więcej kodu | Zawsze (recommended) |
Field | Krótki kod | Trudne testowanie | Nigdy w produkcji |
Setter | Optional dependencies | Mutable state | Opcjonalne zależności |
2. Używaj interfejsów zamiast konkretnych klas
// Dobrze - wstrzykuj interface @Autowired private EmailService emailService; // Źle - wstrzykuj konkretną implementację @Autowired private SmtpEmailService smtpEmailService;
3. Organizuj beany w logiczne pakiety
// Struktura pakietów com.example.service // @Service classes com.example.repository // @Repository classes com.example.config // @Configuration classes com.example.controller // @Controller classes
Tak, @Autowired działa tylko z obiektami zarządzanymi przez Spring container. Obiekty tworzone przez 'new’ nie będą miały wstrzykniętych zależności.
Spring rzuci NoSuchBeanDefinitionException podczas uruchamiania aplikacji. Możesz używać @Autowired(required=false) lub Optional
Gdy masz wiele implementacji tego samego interfejsu i chcesz wybrać konkretną. Alternatywnie możesz użyć @Primary na preferowanej implementacji.
Nie, autowiring ma koszt tylko podczas startu aplikacji. W runtime Spring używa już gotowych referencji do obiektów – brak overhead.
Najlepiej używać constructor injection – możesz wtedy przekazać mock objects w testach. Dla field injection użyj @MockBean w Spring Boot testach.
🚀 Zadanie dla Ciebie
Stwórz system zarządzania książkami z autowiring:
- Utwórz interfejs BookRepository z metodami save() i findById()
- Implementacja InMemoryBookRepository oznaczona @Repository
- BookService z @Autowired BookRepository (użyj constructor injection)
- Dodaj drugą implementację DatabaseBookRepository
- Użyj @Primary lub @Qualifier do wyboru implementacji
Przetestuj czy Spring poprawnie wstrzykuje zależności i czy możesz przełączać między implementacjami.
Przydatne zasoby:
- Spring Framework Reference – @Autowired
- Spring Guides – Managing Transactions
- Martin Fowler – Dependency Injection
Jakiej strategii autowiring używasz najczęściej? Constructor czy field injection? Dlaczego wybrałeś takie podejście?