Injectarea de dependențe
Introducere
Pentru a pune în context programarea web, unul dintre motivele pentru care limbaje precum Java și C# sunt populare în dezvoltarea aplicațiilor este suportul pentru reflecție la runtime. Cu alte cuvinte, programul poate face introspecție asupra propriului cod la runtime și poate, de exemplu, să creeze instanțe de obiecte fără a fi programat explicit în acest sens.
Acest aspect a facilitat implementarea a dependency injection în aceste limbaje, care constă în instanțierea componentelor la runtime, de la cele mai simple la cele mai complexe. Instanțele acestor componente sunt apoi furnizate ca parametri pentru instanțierea altor componente. Unul dintre principalele concepte care stau la baza dezvoltării aplicațiilor moderne este Injectarea de Dependențe (Dependency Injection - DI).
Inversion of Control (IoC)
Inversion of Control (IoC) este un principiu fundamental al programării modulare, care presupune că un framework sau un container gestionează ciclul de viață al obiectelor și dependențele acestora. În Spring, acest lucru este realizat prin IoC Container, care se ocupă cu crearea, configurarea și gestionarea obiectelor aplicației (cunoscute sub numele de beans).
Spring Beans
Un bean este un obiect gestionat de containerul Spring. Fiecare componentă a aplicației, fie că este un serviciu, un repository sau un controller, poate fi definit ca un bean și injectat automat în alte componente.
Declararea unui Bean în Spring
În Spring, putem defini un bean folosind adnotări sau fișiere de configurare XML. Cel mai utilizat mod este prin adnotări Java:
import org.springframework.stereotype.Service;
@Service
public class UserService {
public String getUser() {
return "user";
}
}
Adnotarea @Service indică că UserService este un bean gestionat de containerul IoC al Spring.
Dependency Injection (DI) în Spring
Spring oferă suport puternic pentru dependency injection, permițând dezvoltatorilor să evite instanțierea manuală a obiectelor și să îmbunătățească testabilitatea codului.
Injectarea dependențelor prin Constructor
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
Injectarea dependențelor prin @Autowired
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
}
Chiar dacă injectarea prin atribute este mai simplă, pot apărea probleme cu ordinea injectării atributelor și inițializarea acestora, astfel, se recomandă injectarea prin constructor.
Tipuri de Bean-uri în Spring
Spring oferă trei tipuri principale de scopuri pentru bean-uri:
- Singleton – O singură instanță este creată și reutilizată în întreaga aplicație.
- Prototype – Se creează o nouă instanță de fiecare dată când este solicitat bean-ul.
- Scoped Beans (Request & Session) – Folosite în aplicații web pentru a crea bean-uri per cerere HTTP sau sesiune utilizator.
Exemplu de declarare a unui bean singleton într-un fișier de configurare:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService();
}
}
Servicii în Spring
Serviciile în Spring sunt componente care conțin logica aplicației și sunt adesea implementate ca bean-uri Spring.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class EmailService {
private static final Logger logger = LoggerFactory.getLogger(EmailService.class);
public void sendEmail(String to, String subject, String body) {
logger.info("Trimitere email către: {}", to);
}
}
Acest serviciu poate fi injectat în alte componente astfel:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class EmailController {
private final EmailService emailService;
public EmailController(EmailService emailService) {
this.emailService = emailService;
}
public void sendTestEmail() {
emailService.sendEmail("test@example.com", "Subiect", "Mesaj");
}
}
Lombok în Spring Boot
Lombok este o bibliotecă Java care reduce codul boilerplate prin generarea automată a metodelor getter, setter, constructor, toString(), equals() și hashCode() folosind adnotări specifice.
Pentru a folosi lombok, trebuie adăugata următoarea dependință în pom.xml:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
Exemplu de utilizare:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String id;
private String name;
}
În acest exemplu:
@Datagenerează automat metodelegetter,setter,equals(),hashCode()șitoString().@NoArgsConstructorcreează un constructor fără argumente.@AllArgsConstructorcreează un constructor cu toți parametrii.
Astfel, evităm să scriem manual aceste metode, reducând volumul de cod necesar.
În funcție de cerințele și specificațiile aplicației, lombok vă poate ușura munca sau vă poate încurca. Deoarece codul adăugat de lombok se generează la compilare, vă poate îngreuna debugging-ul. Pe lângă asta, vă pierdeți controlul asupra metodelor pe care vreți să le generați cu lombok. Spre exemplu, dacă vreți să aveți o funcție equals() custom, folosind adnotarea @Data pe clasa respectivă, veți genera o funcție default de egalitate, pe care nu o puteți modifica și care va compara fiecare atribut din cele două obiecte, ceea ce este complet greșit pentru entități doarece nu vrem să comparăm și cheia primară.