ProgramandooIntentándolo

Inyección de dependencias en Spring

La inyección de dependencias es quizás la característica más destacable del core de Spring Framework, que consiste que en lugar de que cada clase tenga que instanciar los objetos que necesite, sea Spring el que inyecte esos objetos, lo que quiere decir que es Spring el que creara los objetos y cuando una clase necesite usarlos se le pasaran (como cuando le pasas un parámetro a un método).

La inyección de dependencias es una forma distinta de diseñar aplicaciones, si estas empezando con Spring (como yo) ya habrás visto que en muchos sitios usan los términos inyección de dependencia (DI) e inversión de control (IoC) de forma indistinta y aunque no son sinónimos, sino que más bien la inyección de dependencia sería una forma de inversión de control.

Pero probablemente te preguntaras en que consiste la inyección de dependencia y que ventajas tiene su uso, pues aquí voy a intentar explicar lo que yo he entendido con unos ejemplos para verlo de una forma más intuitiva, que para ver definiciones ya hay muchos libros.

La DI consiste en que en lugar de que sean las clases las encargadas de crear (instanciar) los objetos que van a usar (sus atributos), los objetos se inyectaran mediante los métodos setters o mediante el constructor en el momento en el que se cree la clase y cuando se quiera usar la clase e cuestión ya estará lista, en cambio sin usar DI la clase necesita crear los objetos que necesita cada vez que se use.

En Spring hay un Contendor DI que es el encargado de inyectar a cada objeto los objetos que necesita (de los que depende) según se le indique ya sea en un archivo de configuración XML o mediante anotaciones.

Con esta pequeña explicación te puedes hacer una idea de en que consiste la inyección de dependencia, vamos ahora a ver cual es el sentido de su uso.

La que nos ofrece la inyección de dependencias es desacoplamiento y también que los objetos son instanciados en el Contenedor DI y se inyectan donde sea necesario de forma que pueden ser reutilizados.

Un ejemplo típico para ver su utilidad es el de una clase que necesita una conexión a base de datos, sin DI si varios usuarios necesitan usar esta clase se tendrán que crear múltiples conexiones a la base de datos con la consiguiente posible perdida de rendimiento, pero usando la inyección de dependencia las dependencias de la clase (sus atributos), son instanciados una única vez cuando se despliega la aplicación y se comparten por todas las instancias de modo que una única conexión a base de datos es compartida por múltiples peticiones. En este caso esta bien que sea una única instancia pero habrá casos en los que no nos interesará esta opción que es la que se usa por defecto por lo que Spring nos da la opción por si queremos que nuestros objetos no usen el patrón singleton.

Y en lo referente al desacoplamiento como no es necesario instanciar en una clase los objetos que necesita sino que son inyectados si la clase que necesita cambia no es necesario modificar nada en la clase que hacia uso de ella.

Y en lo referente al desacoplamiento al no tener que instanciar las dependencias si alguna cambia no hay que modificar nada pues esa clase no estará instanciada en ningún sitio de nuestra clase (Clase c = new clase();).

Vamos a ver unos ejemplos de como se hace la DI mediante settters, mediante el constructor y con el uso de anotaciones para no tener que escribir nada en el xml.

Ejemplo de inyección de dependencia mediante settters

Para que se vean bien las cosas vamos a crear una aplicación de escritorio, en una aplicación web la aplicación es la misma pero como hay más archivos de configuración es más fácil equivocarse aunque como digo la inyección de dependencia se hace exactamente igual, salvo que para una aplicación de escritorio hay que indicarle en el Main donde se encuentra el archivo en el que están declarados los beans y en una aplicación web se hace en el web.xml.

En este primer ejemplo vamos a usar una clase Libro que entre sus atributos tendrá uno de la clase Autor y en el Main mostraremos los datos del libro.


package com.blogspot.programandoointentandolo;

public class Libro {

    private String titulo;
    private Autor autor;
    private String genero;
    private String editorial;
    private int edicion;
    private int paginas;

    // Getters y Setters
}

package com.blogspot.programandoointentandolo;

public class Autor {

    private String nombre;
    private String apellido;
 
    // Getters y Setters
} 

La clase Libro es un simple POJO y como vamos a inyectar las dependencias mediante setters pues lógicamente deberá disponer de ellos (lo mismo para Autor) y en el Main para hacer uso de la ID primero debemos de cargar el xml en el que estarán definidos los beans y luego obtenemos el que nos interesa (libro).


package com.blogspot.programandoointentandolo;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

    public static void main(String[] args) {
        BeanFactory factory = new ClassPathXmlApplicationContext("META-INF/spring/app-context.xml");
        Libro libro = (Libro) factory.getBean("libro");
        System.out.println("- " + libro.getTitulo());
        System.out.println("- " + libro.getAutor().getNombre() + " " + libro.getAutor().getApellido());
        System.out.println("- " + libro.getEditorial());
        System.out.println("- " + libro.getGenero());
        System.out.println("- " + libro.getEdicion());
        System.out.println("- " + libro.getPaginas());
    }
}

Vamos con el app-context.xml que será donde definamos los beans para poder inyectarlos donde lo necesitemos:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <bean id="libro" class="com.blogspot.programandoointentandolo.Libro">
        <property name="titulo" value="Aprendiendo Spring"/>
        <property name="autor" ref="autor"/>
        <property name="genero" value="Aventuras"/>
        <property name="edicion" value="2"/>
        <property name="paginas" value="257"/>
    </bean>

    <bean id="autor" class="com.blogspot.programandoointentandolo.Autor">
        <property name="nombre" value="Luis" />
        <property name="apellido" value="Perez" />
    </bean>
</beans>

Viendo el código anterior te puedes hacer una idea bastante buena de que es lo que hace sin ninguna explicación pero vamos a explicarlo. En primer lugar las clases se definen con la etiqueta <bean> y se deben de indicar el atributo class que indica donde esta la clase de la que será el bean y si se quiere usar en otro lugar el atributo id que nos servirá como identificador del bean.

Dentro de cada bean se pueden indicar las propiedades que se quieren inyectar con la etiqueta property, el atributo name hace referencia al nombre del atributo que se quiere inyectar, por ejemplo el primero es para inyectar el titulo, y después puede tener un atributo value si le queremos inyectar un String, int,… y el atributo ref que lo usaremos cuando queramos inyectar otro bean, es el caso de autor, y en el atributo ref debemos de poner el id del bean que queremos inyectarle, en este caso también es autor.

Y con esto cuando ejecutemos la aplicación veremos como se nos imprimen los valores que hemos introducido en xml sin haber tenido que instanciar en ningún momento ningún objeto ni de la clase Libro ni de la clase Autor. Pues así de sencilla es la inyección de dependencia.

Ejemplo de inyección de dependencia mediante el constructor

Además de poder inyectar mediante métodos setters también se puede inyectar mediante constructor aunque es mejor la primera opción. Vamos a ver en este ejemplo como inyectar las propiedades del Autor mediante constructor para lo cual la clase Autor deberá de tener un constructor con los parámetros nombre y apellido y no hace falta que tenga métodos setters. No hace falta modificar otra cosa del resto del código java.

Y el xml para poder inyectar mediante constructor en el bean autor es este:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <bean id="libro" class="com.blogspot.programandoointentandolo.Libro">
        <property name="titulo" value="Aprendiendo Spring"/>
        <property name="autor" ref="autor"/>
        <property name="genero" value="Aventuras"/>
        <property name="edicion" value="2"/>
        <property name="paginas" value="257"/>
    </bean>

    <bean id="autor" class="com.blogspot.programandoointentandolo.Autor">
        <constructor-arg value="Luis" />
        <constructor-arg value="Perez" />
    </bean>
</beans>

Los parámetros del libro los seguimos inyectando mediante setters pero para el autor usaremos el constructor para lo que debemos de cambiar property por constructor-arg y solo le indicamos el value o ref si fuese el caso, pero no hay que indicar el name ya que estos atributos se pasaran en orden al constructor por lo que debemos de tener cuidado de ponerlos en el orden correcto.

Ejemplo de inyección de dependencia con anotaciones

De las dos formas anteriores era necesario indicar en el archivo de configuración que beans podían ser inyectados en otros y sobre los que se querían inyectar. Mediante las anotaciones podemos hacer ambas cosas o solo una si se quiere, por ejemplo podemos declarar los beans en el xml y usar anotaciones para inyectarlos en lugar de usar <property name=”autor” ref=”autor”>.

Antes de empezar voy a poner una lista de las anotaciones que voy a comentar:

  • @Component:Sustituye la declaración del bean en el xml.
  • @Autowired:Sustituye la declaración de los atributos del bean en el xml.
  • @Qualifier(“nombreBean”):Sirve para indicar que clase es la que se debe inyectar.
  • @Required:Indica si el atributo es obligatorio.
  • @Service, @Repository y @Controller:Son estereotipos de @Component y se usan para indicar que la clase sera un servicio (@Service), una clase de acceso a datos (@repository) o un controlador (@Controller).
  • @PostConstruct:Ejecuta el metodo con esta anotación despues de crear el objeto.
  • @PreDestroy:Ejecuta el metodo con esta anotación antes de destruir el objeto.
  • @Scope:Sirve para indicar el ambito en el que se encontrara el bean.

Las anotaciones de la lista anterior son propias de Spring pero también se pueden usar anotaciones propias del standar Java EE como las 2 siguientes:

  • @Inject:Se puede usar en lugar de @Autowired.
  • @Resource(“nombreBean”):Sustituye el uso de las anotaciones @Autowired y @Qualifier(“nombreBean”) de forma que es necesaria una sola anotación.

@Autowired

Para empezar vamos a ver la anotación @Autowired que nos permite no tener que definir la propiedad que se quiere inyectar en el xml dentro del bean. La anotación @Autowired se puede poner encima del atributo que se quiere inyectar, encima del método setter de dicho método o también encima del constructor y dependiendo de donde se ponga la inyección se haría por atributo, por setter o por constructor como es lógico.

Respecto al código que teníamos sin usar anotaciones hay que modificar la clase Libro añadiendo la anotación @Autowired de cualquiera de las 3 formas que decía, en este ejemplo anotamos el atributo.


import org.springframework.beans.factory.annotation.Autowired;

public class Libro {

    private String titulo = "Aprendiendo Spring";

    @Autowired
    private Autor autor;

    private String genero = "Aventuras";
    private String editorial = "Una";
    private int edicion = 2;
    private int paginas = 257;

    // Setters y getters
}

Y en el xml quedaría así:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <!-- Habilita el uso de anotaciones -->
    <context:annotation-config />

    <bean id="libro" class="com.blogspot.programandoointentandolo.Libro"/>

    <bean id="autor" class="com.blogspot.programandoointentandolo.Autor">
        <constructor-arg value="Luis" />
        <constructor-arg value="Perez" />
    </bean>
</beans>

El bean autor sigue igual ya que no hemos hecho ninguna anotación en el, pero en el bean libro ahora no hay ningún atributo ya que ahora hemos usado la anotación @Autowired para inyectar el autor, y el resto de campos que son simples strings e ints simplemente están inicializados en la clase aunque en un ejemplo más “serio” pues se le darían valores con los setters por ejemplo aunque para el caso de ver como funcionan las anotaciones eso no aporta nada.

Además de que en el bean libro ya no están los atributos para poder usar la anotación @Autowired es necesario incluir <context:annotation-config /> para indicar que vamos a usar esta anotación.

@Component

Esta anotación nos evita la necesidad de declarar el bean en el xml, vamos a anotar las clases Libro y Autor con @Component y de este modo ya no tendremos ningún bean en el xml, de modo que el xml solo tendrá que contener la siguiente línea en la que le indicamos el paquete apartir del que se buscaran las clases anotadas con @Component o cualquiera de sus estereotipos y también sustituye a <context:annotation-config /> que usemos por si solo queríamos usar @Autowired.


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <!-- Habilita el uso de anotaciones -->
    <context:component-scan base-package="com.blogspot.programandoointentandolo" />
</beans>

Y en lo referente a las clases Autor y Libro quedarian de la siguiente forma:


package com.blogspot.programandoointentandolo;

import org.springframework.stereotype.Component;

@Component
public class Autor {

    private String nombre = "Luis";
    private String apellido = "Perez";

    public Autor(){}
 
    // Setters y getters 
}

package com.blogspot.programandoointentandolo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Libro {

    private String titulo = "Aprendiendo Spring";

    @Autowired
    private Autor autor;

    private String genero = "Aventuras";
    private String editorial = "Una";
    private int edicion = 2;
    private int paginas = 257;
 
    public Libro() {}

    // Setters y getters   
}

Además de anotar la clase con @Component tambien hay que añadir un constructor sin parámetros, en el caso de que tuviésemos alguno con parámetros ya que al usar la anotación @Component el objeto se crea con este constructor y sino lo tenemos no compilará.

@Qualifier

Con esta anotación podemos indicar el id del bean que se quiere inyectar, esta anotación se usa cuando el atributo que vamos a inyectar es una interfaz de la que hay varias implementaciones y entonces será mediante esta anotación con la que le diremos cual es la clase que queremos inyectar. También se puede usar por ejemplo si en el xml declaramos varios beans que aunque sean de la misma clase (tengan el mismo atributo class) tengan un id distinto.

Aunque en los ejemplos que he puesto no he puesto ninguna interfaz pues no aporta nada a la explicación lo normal será que las clases que se inyectan sean interfaces para hacer el código más desacoplado y facilitar las posibles futuras modificaciones.

Por ejemplo si en lugar de la clase autor directamente tuviésemos una interfaz AutorInterfaz y dos clases AutorDesconocido y AutorConocido


package com.blogspot.programandoointentandolo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class Libro {

    private String titulo = "Aprendiendo Spring";

    @Autowired
    @Qualifier("autorconocido")
    private AutorInterfaz autor;

    private String genero = "Aventuras";
    private String editorial = "Una";
    private int edicion = 2;
    private int paginas = 257;
 
    public Libro() {}

    // Setters y getters   
}

package com.blogspot.programandoointentandolo;

import org.springframework.stereotype.Component;

@Component("autordesconocido")
public class AutorDesconocido implements AutorInterfaz{

    private String nombre = "Luis";
    private String apellido = "Perez";

    // Setters y getters 
}

package com.blogspot.programandoointentandolo;

import org.springframework.stereotype.Component;

@Component("autorconocido")
public class AutorConocido implements AutorInterfaz{
 
    private String nombre = "Antonio";
    private String apellido = "Fernandez"; 

    // Setters y getters 
}

Como en @Qualifier hemos indicado autorconocido se inyectara la clase AutorConocido pero si quisiésemos cambiar para que se inyectara AutorDesconocido o cualquier otra clase que implementase AutorInterfaz solo tendríamos que indicar en @Qualifier el nombre del bean que queremos inyectar.

@Required

Esta anotación creo que esta bastante claro para que se usa, si queremos que un campo sea obligatorio lo anotaremos con @Required. Para que el autor fuese obligatoria la clase Libro sería así:


package com.blogspot.programandoointentandolo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Libro {

    private String titulo = "Aprendiendo Spring";

    @Autowired
    @Qualifier("autorconocido")
    @Required
    private AutorInterfaz autor;

    private String genero = "Aventuras";
    private String editorial = "Una";
    private int edicion = 2;
    private int paginas = 257;
 
    public Libro() {}

    // Setters y getters   
}

@PostConstruct y @PreDestroy

Estas son anotaciones para métodos y las usaremos si queremos que se ejecute un método justo después de que se haya creado la clase (objeto de la clase) o antes de que se destruya.


import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class Libro {

    private String titulo = "Aprendiendo Spring";

    @Autowired
    @Qualifier("autorconocido")
    @Required
    private AutorInterfaz autor;

    private String genero = "Aventuras";
    private String editorial = "Una";
    private int edicion = 2;
    private int paginas = 257;
 
    public Libro() {}

    @PostConstruct
    public void saludar(){
        System.out.println("Hola");
    }

    @PreDestroy
    public void despedirse(){
        System.out.println("Adios");
    }

    // Setters y getters   
}

@Scope

Con esta anotación indicamos cuando se crearan instancias de la clase con esta anotación y en que ámbito. Si no se usa la anotación @Scope (valor por defecto) el scope es singleton. Los posibles valores son:

  • singleton: Se crea una unica instancia del bean para toda la aplicación.
  • prototype: Se crea una nueva instacia del bean cada vez.
  • request: Se crea una nueva instacia del bean para cada petición HTTP request.
  • session: Se crea una nueva instacia del bean por sesión HTTP.
  • globalSession: Se crea una nueva instacia del para cada sesión HTTP global.

Las 3 ultimas aunque supongo que se ve por el nombre solo son para aplicaciones web mientras que las dos primeras pueden usarse en cualquier tipo de aplicación Spring.

Y para finalizar si quisiésemos que se crease una instancia de la clase Libro cada vez usaríamos @Scope(“prototype”).


import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class Libro {

    private String titulo = "Aprendiendo Spring";

    @Autowired
    @Qualifier("autorconocido")
    @Required
    private AutorInterfaz autor;

    private String genero = "Aventuras";
    private String editorial = "Una";
    private int edicion = 2;
    private int paginas = 257;
 
    public Libro() {}

    @PostConstruct
    public void saludar(){
        System.out.println("Hola");
    }

    @PreDestroy
    public void despedirse(){
        System.out.println("Adios");
    }

    // Setters y getters   
}

Después de ver como hacer las inyecciones de dependencias con anotaciones probablemente te preguntaras si es mejor o peor usar anotaciones. Desde el punto de vista de que es más fácil y más rápido esta claro que ganan las anotaciones y desde el punto de vista de tener todo centralizado mejor usar el xml porque con las anotaciones esta repartido por toda la aplicación por lo que para modificar algo sería necesario buscarlo, modificarlo y recompilarlo mientras que con el xml todo esta en el mismo sitio y con cambiar el xml esta todo listo sin hacer nada más.

Entonces podríamos decir que las anotaciones son ideales durante la fase de desarrollo porque el código esta cambiando constantemente y cuanto más rápido sea hacer un cambio mejor y una vez que la aplicación ya sea “estable” se sustituyen las anotaciones y de este modo tenemos las ventajas de ambas opciones en los momentos en las que los necesitamos, aunque no se si tiene demasiado sentido hacer esto…

Y como siempre si quieres el código de los ejemplo lo puedes descargar aquí.