Inyección de Dependencias (DI) en Spring Boot

· 3 min de lectura
Inyección de Dependencias (DI) en Spring Boot
Photo by Kenny Eliason / Unsplash

Cuando trabajamos con Spring Boot, la Inyección de Dependencias aparece muy pronto. Al principio se acepta casi sin cuestionarla: se agregan anotaciones, el proyecto arranca y la aplicación funciona. Sin embargo, comprender por qué funciona así es lo que marca la diferencia entre usar un framework y realmente dominarlo.

La Inyección de Dependencias no es una característica aislada. Es la consecuencia directa de una forma distinta de pensar el diseño del software, basada en la Inversión de Control.

Inversión de Control e Inyección de Dependencias: idea y mecanismo

La Inversión de Control es el principio. Nos dice que una clase no debería encargarse de crear los objetos que necesita para funcionar. Su responsabilidad es ejecutar su lógica, no decidir cómo se construyen sus dependencias.

La Inyección de Dependencias es el mecanismo que hace posible esa idea. Es el proceso mediante el cual Spring crea los objetos, los gestiona y los conecta entre sí en el momento adecuado.

Dicho de forma simple:
la clase declara lo que necesita, y Spring se encarga de proporcionárselo.

Cómo entiende Spring tu aplicación cuando arranca

Al iniciar la aplicación, Spring Boot recorre el proyecto buscando clases que deban ser gestionadas por el framework. Al encontrarlas, las instancia y las guarda en su contenedor interno.

Luego analiza cómo se relacionan entre sí. Observa los constructores y detecta qué dependencias necesita cada clase para existir. Si una clase requiere otra, Spring la busca en el contenedor y la inyecta automáticamente.

Este proceso ocurre antes de que tu código de negocio se ejecute, por eso cuando lo usas, todo “ya está listo”.

Estructura del proyecto: tres archivos, tres responsabilidades

Imaginemos un proyecto con solo tres archivos. No hay capas innecesarias ni complejidad artificial. Cada clase tiene una responsabilidad clara.

Usaremos la analogía del restaurante para que el flujo sea fácil de seguir.

La dependencia: el almacén de ingredientes

El primer archivo representa un recurso. Es algo que otras clases necesitan, pero que no depende de ellas.

// CLASE B
@Component
public class AlmacenIngredientes {

    public String entregarVerduras() {
        return "Tomates frescos y lechuga";
    }
}

Este almacén tiene una responsabilidad muy concreta: entregar ingredientes.
No sabe quién los va a usar, ni cuándo, ni para qué. Y eso está bien.

La anotación @Component le indica a Spring que esta clase debe ser detectada, creada y gestionada por el framework. A partir de ese momento, Spring se encarga de su ciclo de vida.

El consumidor: el cocinero que declara su necesidad

El segundo archivo es el corazón del ejemplo. Aquí aparece la lógica de negocio.

// CLASE B
@Service
public class Cocinero {

    private final AlmacenIngredientes almacen;

    public Cocinero(AlmacenIngredientes almacen) {
        this.almacen = almacen;
    }

    public void cocinar() {
        String ingredientes = almacen.entregarVerduras();
        System.out.println("👨‍🍳 Cocinando una ensalada con: " + ingredientes);
    }
}

Aquí ocurre algo fundamental.
El cocinero no crea el almacén. No usa new. No decide cómo se instancia ni de dónde sale. Simplemente declara, a través del constructor, que lo necesita para poder funcionar.

Este detalle es clave desde el punto de vista del diseño. El constructor deja claro que el cocinero no puede existir sin su dependencia. La clase es honesta sobre sus requisitos y queda completamente inicializada desde el momento en que se crea.

Eso es Inyección de Dependencias aplicada correctamente.

La ejecución: el restaurante en funcionamiento

El tercer archivo es el punto de entrada de la aplicación. Aquí no se conectan dependencias ni se construyen objetos manualmente. Todo eso ya ocurrió antes

@SpringBootApplication
public class RestauranteApplication implements CommandLineRunner {

    @Autowired
    private Cocinero cocineroPrincipal;

    public static void main(String[] args) {
        SpringApplication.run(RestauranteApplication.class, args);
    }

    @Override
    public void run(String... args) {
        System.out.println("--- INICIO DEL SERVICIO ---");
        cocineroPrincipal.cocinar();
        System.out.println("--- FIN DEL SERVICIO ---");
    }
}

Cuando la aplicación arranca, Spring entrega un Cocinero ya completamente construido, con su AlmacenIngredientescorrectamente inyectado. El restaurante abre sus puertas y el cocinero puede trabajar sin preocuparse por nada más.

Qué hace Spring paso a paso con este código

Primero, Spring escanea el proyecto y detecta el almacén y el cocinero. Luego crea una instancia del almacén y la guarda en su contenedor. Cuando llega el turno de crear el cocinero, analiza su constructor, ve que necesita un AlmacenIngredientes y se lo entrega automáticamente.

El resultado es que, cuando se ejecuta el método cocinar, la dependencia ya está disponible y el sistema funciona sin acoplamientos rígidos.

Por qué este ejemplo es más importante de lo que parece

Este patrón evita dependencias fuertes, facilita las pruebas unitarias y permite cambiar implementaciones sin romper el resto del código. Además, empuja naturalmente hacia buenas prácticas como la inmutabilidad y el respeto por los principios SOLID.

Todo esto nace de una decisión simple: no crear dependencias dentro de las clases.

La idea final para llevarse

La Inyección de Dependencias no es magia ni una moda del framework. Es la consecuencia lógica de escribir clases que se enfocan en su responsabilidad y delegan el resto.

Cuando una clase deja de crear lo que necesita y simplemente lo declara, el diseño mejora de inmediato. Spring Boot solo automatiza ese proceso y lo hace consistente en toda la aplicación.

Entender esto es el punto en el que se deja de “copiar ejemplos” y se empieza a escribir código profesional.