Inversió de Control (IoC) i Injecció de Dependències (DI) a Spring Boot
Spring Boot es basa en dos conceptes essencials que simplifiquen el desenvolupament d’aplicacions: Inversió de Control (IoC) i Injecció de Dependències (DI). Estos conceptes (que poden pareixer abstractes i dificils de compendre al principi), són claus per entendre com Spring Boot gestiona automàticament els components del projecte.
1. Inversió de Control (IoC)
La Inversió de Control (IoC) és un principi de disseny que canvia la responsabilitat de crear i gestionar els objectes d’una aplicació.
No som nosaltres qui creem els objectes, és el contenidor IoC de Spring qui:
- Crea els objectes que necessita l’aplicació (anomenats beans).
- Gestiona el cicle de vida d’aquests objectes.
- Proporciona aquests objectes quan una classe els necessita.
En resum, IoC permet que nosaltres sigam els que demanen els objectes, però no els que els creen.
Als objectes generats per Spring se’ls anomena beans. Aquests beans són instàncies de classes que són gestionades pel contenidor IoC.
Què és el contenidor IoC de Spring?
El contenidor IoC de Spring és el nucli del framework Spring. La seua funció principal és gestionar els objectes (beans) i les seues dependències dins d’una aplicació.
Com funciona el contenidor IoC?
- Inicialització del contenidor:
- El contenidor IoC s’inicia amb el mètode
SpringApplication.run()
:@SpringBootApplication public class Aplicacio { public static void main(String[] args) { SpringApplication.run(Aplicacio.class, args); // Arranca el contenidor IoC } }
- Durant l’arrencada, Spring escaneja les classes del projecte per trobar anotacions com
@Component
,@Service
,@Repository
o@Controller
.
- El contenidor IoC s’inicia amb el mètode
- Creació de beans:
- Per cada classe detectada amb aquestes anotacions, Spring crea una instància (bean) i la registra al contenidor IoC.
- Gestió de dependències:
- Si una classe (per exemple, un servei) necessita un altre objecte (per exemple, un repositori), Spring detecta aquesta necessitat (a través de
@Autowired
) i injecta automàticament la dependència.
- Si una classe (per exemple, un servei) necessita un altre objecte (per exemple, un repositori), Spring detecta aquesta necessitat (a través de
- Gestió del cicle de vida dels beans:
- Spring controla quan els beans són inicialitzats, utilitzats i destruïts. També pot executar mètodes inicials (
@PostConstruct
) o finals (@PreDestroy
).
- Spring controla quan els beans són inicialitzats, utilitzats i destruïts. També pot executar mètodes inicials (
Els avantatges que ens proporciona el contenidor IoC són, entre d’altres:
- Redueix el coupling: Les classes no depenen d’implementacions concretes.
- Facilita la reutilització: Els beans es poden compartir entre diferents components.
- Simplifica la configuració: Les dependències es gestionen automàticament.
Exemple d’IoC
- 1.- Sense IoC (creació manual d’objectes): En aquest cas, cada classe crea les seues dependències utilitzant el mètode
new
.
public class Servei {
private Repositori repositori;
public Servei() {
// El servei crea la dependència manualment
this.repositori = new Repositori();
}
public void executar() {
// Acció utilitzant el repositori
System.out.println("Executant servei...");
repositori.guardar(); // Crida al mètode guardar del repositori
}
}
Problemes amb aquest codi:
- Acoblament (Coupling) fort: El
Servei
està lligat a la implementació concreta deRepositori
. Si vols canviar de repositori, has de modificar el codi. - Dificultat per fer proves: No pots substituir fàcilment el
Repositori
per una versió falsa (mock) en les proves. - Codi menys flexible i difícil de mantenir.
- 2.- Amb IoC (gestió automàtica de Spring): Ara és el contenidor IoC de Spring qui crea i injecta automàticament el repositori al servei.
Repositori:
@Repository
public class Repositori {
public void guardar() {
// Simulació de guardar dades
System.out.println("Dades guardades al repositori!");
}
}
Servei:
@Service
public class Servei {
private Repositori repositori;
// La dependència es proporciona automàticament amb IoC
@Autowired
public Servei(Repositori repositori) {
this.repositori = repositori; // La dependència és injectada per Spring
}
public void executar() {
// Acció utilitzant el repositori
System.out.println("Executant servei amb IoC...");
repositori.guardar(); // Crida al mètode guardar del repositori
}
}
Classe principal:
@SpringBootApplication
public class Aplicacio {
public static void main(String[] args) {
// Inicialitza l'aplicació i el contenidor IoC de Spring
SpringApplication.run(Aplicacio.class, args);
}
}
Funcionament del contenidor IoC?
- Detecta les classes anotades amb:
@Repository
,@Service
,@Controller
,@RestController
, etc.
- Crea automàticament instàncies d’aquestes classes (anomenades beans).
- Gestiona el cicle de vida dels beans i injecta les dependències quan són necessàries.
2. Injecció de Dependències (DI)
La Injecció de Dependències (DI) és una tècnica que implementa IoC. Permet que el contenidor IoC de Spring proporcione automàticament els objectes que una classe necessita. Així, les dependències es passen a la classe des de fora.
- En lloc de crear un objecte amb
new
, Spring l’injecta automàticament.
Formes d’injectar dependències a Spring
Spring Boot ofereix tres formes principals d’injecció de dependències:
-
Injecció per constructor (recomanada): Spring passa les dependències necessàries a través del constructor. És la millor opció perquè assegura que la classe estiga correctament inicialitzada.
Exemple:
@Service public class Servei { private final Repositori repositori; // Dependència declarada com a final @Autowired public Servei(Repositori repositori) { this.repositori = repositori; // La dependència es passa pel constructor } public void executar() { repositori.guardar(); // Crida al mètode guardar del repositori } }
-
Injecció per mètode setter: Spring utilitza un mètode setter per passar la dependència. Aquesta opció és útil si la dependència és opcional.
Exemple:
@Service public class Servei { private Repositori repositori; // Dependència no final perquè es pot modificar @Autowired public void setRepositori(Repositori repositori) { this.repositori = repositori; // La dependència es passa pel setter } public void executar() { repositori.guardar(); // Crida al mètode guardar del repositori } }
-
Injecció directa en camps: Spring injecta la dependència directament al camp. És l’opció més senzilla, però no és la més recomanada perquè dificulta proves i no assegura que l’objecte estiga completament inicialitzat.
Exemple:
@Service public class Servei { @Autowired private Repositori repositori; // Dependència injectada directament public void executar() { repositori.guardar(); // Crida al mètode guardar del repositori } }
Exemple de IoC i DI
- Repositori: ```java @Repository public class PaisRepository { public String[] obtenirPaisos() { // Retorna una llista de països return new String[]{“Espanya”, “França”, “Itàlia”}; } }
// Este Repositori no hereta de cap classe ni implementa cap interfície (no hereta de CrudRepository ni implementa Cr), però Spring el detecta com a bean per l’anotació @Repository
- **Servei:**
```java
@Service
public class PaisService {
private final PaisRepository paisRepository; // Dependència injectada per constructor
@Autowired
public PaisService(PaisRepository paisRepository) {
this.paisRepository = paisRepository; // Injecció de la dependència
}
public void mostrarPaisos() {
// Obté els països del repositori i els mostra per pantalla
String[] paisos = paisRepository.obtenirPaisos();
for (int i = 0; i < paisos.length; i++) {
System.out.println(paisos[i]); // Mostra cada país
}
}
}
- Controlador:
@RestController @RequestMapping("/paisos") public class PaisController { private final PaisService paisService; // Dependència injectada per constructor @Autowired public PaisController(PaisService paisService) { this.paisService = paisService; // Injecció de la dependència } @GetMapping public void llistarPaisos() { // Crida al servei per mostrar els països paisService.mostrarPaisos(); } }
Com funciona IoC i DI a l’exemple:
- IoC:
- El contenidor Spring detecta
PaisRepository
,PaisService
iPaisController
gràcies a les anotacions@Repository
,@Service
i@RestController
. - Crea instàncies de cada classe i les registra com a beans.
- El contenidor Spring detecta
- DI:
- Spring injecta automàticament:
PaisRepository
dins dePaisService
.PaisService
dins dePaisController
.
- Spring injecta automàticament:
- Quan accedeixes a l’URL
http://localhost:8080/paisos
, el controlador:- Utilitza el servei per obtenir els països.
- El servei crida al repositori per recuperar les dades.
En Resum: IoC i DI a Spring Boot
Concepte | Com funciona a Spring Boot |
---|---|
Inversió de Control | El contenidor IoC de Spring crea i gestiona automàticament els objectes necessaris. |
Injecció de Dependències | Les dependències entre classes són proporcionades automàticament pel contenidor mitjançant anotacions com @Autowired . |
Amb IoC i DI, Spring Boot automatitza la gestió d’objectes, redueix la dependència entre classes i fa que el codi siga més modular, mantenible i fàcil de provar.