javaspringarquitecturabackend

Spring & Spring Boot: IoC, Beans i Injecció de Dependències Explicats

Els conceptes clau del framework Spring — contenidor IoC, Spring Beans, Injecció de Dependències i com Spring Boot ho facilita tot — amb els errors habituals corregits.

Spring & Spring Boot: IoC, Beans i Injecció de Dependències Explicats

Spring és el framework dominant per construir aplicacions backend en Java. Però la seva “màgia” — per què les coses funcionen com funcionen — sovint es passa per alt. Aquest post cobreix els conceptes fonamentals des de zero, amb les imprecisions habituals corregides.


Spring Framework vs Spring Boot

Spring Framework és la base no-opinionated. Proporciona les eines (IoC, transaccions, MVC, etc.) però requereix configurar-ho tot manualment. Per exemple, per definir un controlador cal treballar directament amb servlets.

Un servlet és una classe Java que amplia les capacitats d’un servidor — específicament per gestionar peticions i respostes HTTP.

Spring Boot és una capa opinionated construïda sobre Spring Framework. Proporciona autoconfiguració (valors per defecte raonables que cobreixen la majoria de casos d’ús) i permet sobreescriure aquells valors on calgui.

La millora ergonòmica més gran són les dependències starter: una sola dependència com spring-boot-starter-web porta tot el necessari per a una aplicació web — Tomcat, Spring MVC, Jackson — sense haver de cablar cada una per separat.

<!-- Una dependència en lloc de cinc separades -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Característiques clau de Spring Boot:

  • Autoconfigurar — funciona immediatament per a configuracions estàndard
  • Configuració mitjançant anotacions (@Annotation), no XML (tot i que XML segueix funcionant si cal)
  • Dissenyat per a APIs standalone — servidor embegut, sense desplegament separat
  • @SpringBootApplication arrenca tot el context de l’aplicació

El Nucli: Inversió de Control (IoC)

La idea central de Spring és la Inversió de Control: en lloc que el teu codi crei i gestioni les seves pròpies dependències, aquella responsabilitat es delega al framework.

Sense IoC:

public class OrderService {
    private PaymentService paymentService = new PaymentService(); // acoblat
    private EmailService emailService = new EmailService();       // acoblat
}

Amb IoC, Spring gestiona la creació i el cablatge d’objectes. La teva classe simplement declara el que necessita:

@Service
public class OrderService {
    private final PaymentService paymentService;
    private final EmailService emailService;

    public OrderService(PaymentService paymentService, EmailService emailService) {
        this.paymentService = paymentService;
        this.emailService = emailService;
    }
}

Spring crea les instàncies de PaymentService i EmailService i les injecta. OrderService no sap ni li importa com es creen.


Spring Beans

Un Spring Bean és qualsevol objecte gestionat pel contenidor IoC de Spring. Hi ha dues formes de declarar-ne un:

@Component — en una classe. Spring crea i gestiona una instància d’aquesta classe.

@Component
public class EmailService { ... }

@Bean — en un mètode dins d’una classe @Configuration. Útil per a classes de tercers que no pots anotar directament.

@Configuration
public class AppConfig {
    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }
}

Variants especialitzades de @Component (funcionen igual però transmeten intenció):

  • @Service — capa de lògica de negoci
  • @Repository — capa d’accés a dades
  • @Controller / @RestController — capa web

Aclariment important: els beans es descobreixen mitjançant @ComponentScan, que està inclòs a @SpringBootApplication. Aquesta anotació escaneja el paquet i els seus subpaquets en busca de classes anotades amb @Component (o les seves especialitzacions) i les registra al context de l’aplicació.


Àmbits dels Beans

Els beans són singleton per defecte — es crea una única instància compartida en tota l’aplicació. Però és configurable:

ÀmbitComportament
singleton (per defecte)Una instància per Spring Application Context
prototypeNova instància cada cop que es demana
requestUna instància per petició HTTP (apps web)
sessionUna instància per sessió HTTP (apps web)
@Component
@Scope("prototype")
public class ReportGenerator { ... }

Una confusió habitual: els beans no estan “en l’àmbit del singleton de Spring” — viuen al Spring Application Context (el contenidor IoC). Singleton és simplement l’àmbit per defecte, no una propietat fixa de ser un bean.


El Contenidor IoC

El contenidor IoC és el nucli de Spring. És responsable de:

  • Crear beans i gestionar el seu cicle de vida (inicialització, destrucció)
  • Injectar dependències entre beans
  • Emmagatzemar en caché i reutilitzar beans singleton
  • Accés thread-safe a recursos compartits

Beneficis que aporta:

  • Inicialització lazy — els objectes es creen només quan es necessiten
  • Gestió del cicle de vida — la inicialització i neteja correctes eviten memory leaks
  • Configuració centralitzada — redueix el codi de cablatge redundant
  • Funcionalitats integrades — transaccions, pooling, scheduling ja optimitzats

Injecció de Dependències

La Injecció de Dependències (DI) és el mecanisme amb el qual Spring implementa IoC. Desacobla la creació d’objectes del seu ús.

El framework de DI té tres components:

  • Graf — un graf d’objectes que conté totes les dependències del projecte
  • Contenidors — on les dependències es creen
  • Wirings — instruccions que li diuen al framework DI com connectar les dependències

Una dependència és un objecte que un altre objecte necessita per funcionar.

Abans de la Injecció de Dependències

El problema clàssic — Car crea directament el seu Engine:

Abans de DI — Car instancia directament Engine, creant acoblament fort

Car està fortament acoblat a una implementació concreta de Engine. No pots canviar el motor, mockejar-lo en tests, ni reutilitzar Car amb un altre motor sense modificar el seu codi.

Després de la Injecció de Dependències

Spring injecta el Engine a Car a través d’una abstracció:

Després de DI — Engine s'injecta a Car a través d'abstracció, desacoblant els dos

Car ara depèn d’una interfície (o tipus abstracte), no d’una implementació concreta. Spring decideix quina implementació injectar. Això fa el codi testejable, flexible i fàcil de canviar.

Tres Formes d’Injectar a Spring

Injecció per constructor (recomanada):

@Service
public class CarService {
    private final Engine engine;

    public CarService(Engine engine) {  // Spring injecta això
        this.engine = engine;
    }
}

Injecció per camp (còmoda però més difícil de testar):

@Service
public class CarService {
    @Autowired
    private Engine engine;
}

Injecció per setter (útil per a dependències opcionals):

@Service
public class CarService {
    private Engine engine;

    @Autowired
    public void setEngine(Engine engine) {
        this.engine = engine;
    }
}

La injecció per constructor és l’enfocament preferit — fa les dependències explícites, suporta immutabilitat (camps final) i facilita els tests unitaris sense necessitar un context de Spring.


Imperatiu vs Reactiu — Aclarint una Confusió Habitual

Un error freqüent: “programació imperativa = síncrona, programació reactiva = asíncrona”.

Això és incorrecte. La distinció correcta:

Programació imperativa significa que describes com fer alguna cosa, pas a pas. El flux de control és explícit. Això no té res a veure amb síncrona o asíncrona — CompletableFuture, callbacks i servlets asíncrons són tots imperatius i asíncrons.

Programació reactiva significa que describes què fer en resposta a esdeveniments. Les dades se t’envien quan estan disponibles, i tu reacciones a elles. La contrapressió és nativa.

L’eix real:

SíncronaAsíncrona
ImperativaCodi bloquejant tradicionalCompletableFuture, callbacks
Reactiva(rar)Spring WebFlux, Project Reactor

En el context de Spring:

  • Spring MVC — imperatiu, bloquejant per defecte (es pot fer asíncron)
  • Spring WebFlux — reactiu, no bloquejant, construït sobre Project Reactor

Pots escriure codi imperatiu asíncron (CompletableFuture). També pots escriure codi reactiu síncron (poc habitual). Són eixos independents.


Spring MVC vs Spring WebFlux

Spring MVCSpring WebFlux
ModelImperatiu (bloquejant)Reactiu (no bloquejant)
ServidorTomcat (un fil per petició)Netty (event loop)
Tipus de retornString, ResponseEntity<T>Mono<T>, Flux<T>
Quan usarApps CRUD, càrrega moderadaI/O d’alta concurrència, streaming
Corba d’aprenentatgeBaixaAlta

Per a una anàlisi en profunditat del costat reactiu, vegeu el post complementari: Project Reactor: Guia Pràctica de Programació Reactiva en Java.


Resum

  • Spring Framework = potent però manual. Spring Boot = opinionated, autoconfigurar, llest per a producció ràpidament
  • IoC = Spring gestiona la creació d’objectes. Tu declares necessitats, Spring els cabla
  • Spring Beans = objectes gestionats pel contenidor IoC. @Component (nivell classe) o @Bean (nivell mètode)
  • Àmbits de beans = singleton per defecte, però configurable. Els beans viuen al Application Context, no “en el singleton”
  • @ComponentScan (inclòs a @SpringBootApplication) és qui descobreix els beans
  • Injecció de Dependències = Spring injecta dependències per constructor, camp o setter. Constructor és el preferit
  • Imperatiu ≠ síncron. La distinció correcta és imperatiu (flux de control explícit) vs reactiu (orientat a esdeveniments, push-based)

Lectures Recomanades