Spring Framework 002 - Componentes con anotaciones

De ChuWiki

Hemos visto como instanciar clases y hacer que se vean en ejemplos anteriores, usando un fichero XML de configuración de Spring. Spring permite hacer esto mismo con anotaciones, o una mezcla de fichero de configuración XML y anotaciones. Veamos un ejemplo. Lo tienes en ListaContactosMain.java

Configurar XML de Spring para que lea anotaciones[editar]

Lo primero es indicar a Spring que vamos a poner anotaciones en las clases y que queremos que las lea. Para ello ponemos lo siguiente en el fichero XML de configuración de Spring.

<?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">

    <context:component-scan base-package="com.chuidiang.pruebas.annotated_spring"/>

    ...
</beans>

En primer lugar, es importante poner en la cabecera las líneas 4, 7 y 8 del trozo de código XML anterior, relativas a context. Esto nos permite poner la línea de context:component-scan. En esta línea indicamos a Spring que debe buscar anotaciones y le estamos indicando con base-package en qué paquete de java debe buscar clases con anotaciones. Busca en este paquete y en los subpaquetes de ese en cualquier nivel de profundidad.

Anotación @Component[editar]

Una vez hecho esto, podemos hacer clases dentro de este paquete o subpaquetes de este y ponerles al anotación @Component. Spring, cuando arranque, buscará clases con esta anotación y las instanciará. Igual que hacía en los ejemplos anteriores desde el fichero de configuración XML de Spring con el tag <bean>. Vamos a hacer una clase que sea una lista de contactos con esta anotación

package com.chuidiang.pruebas.annotated_spring;

import org.springframework.stereotype.Component;

/**
 * @author fjabellan 03/06/2023
 */
@Component
public class ListaContactos {
 ....
}

Aquí es importante fijarse que hemos puesto la clase en un package que está dentro del component-scan que hemos indicado en el fichero Spring XML. Y la anotación @Component en la clase. Cuando arranque Spring, instanciará esta clase.

Anotación @Autowired[editar]

Nos queda la segunda parte del ejemplo que sería instanciar otras clases con Spring y hacer que ListaContactos las vea, similar a como hicimos en Inyección de dependencias.

Usaremos la misma clase Persona del ejemplo de ese enlace. En el fichero Spring XML instanciamos varias personas, igual que hicimos en aquel ejemplo

    <bean id="Juan" class="com.chuidiang.pruebas.spring.Persona">
        <property name="id" value="1"/>
        <property name="nombre" value="Juan"/>
        <property name="fechaNacimiento" value="11/23/1943"/>
    </bean>

    <bean id="Pedro" class="com.chuidiang.pruebas.spring.Persona">
        <property name="id" value="2"/>
        <property name="nombre" value="Pedro"/>
        <property name="fechaNacimiento" value="1/1/1999"/>
    </bean>

    <bean id="Antonio" class="com.chuidiang.pruebas.spring.Persona">
        <constructor-arg name="fechaNacimiento" value="3/13/2001"/>
        <constructor-arg name="nombre" value="Antonio"/>
        <constructor-arg name="id" value="3"/>
    </bean>

Aquí un detalle. Lo hacemos desde el fichero XML porque queremos varias instancias de persona y con datos concretos en cada una de ellas. Con la anotación @Component no es fácil (o quizás ni siquiera posible) obtener varias instancias con valores concretos y no queremos complicar el ejemplo.

Para que ListaContactos reciba estas instancias de personas, debemos añadirle una List<Persona> donde Spring inyectará todas las instancias de Persona que encuentre.

package com.chuidiang.pruebas.annotated_spring;

import com.chuidiang.pruebas.spring.Persona;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ListaContactos {
    @Autowired
    private List<Persona> contactos;

    public String toString(){
        return "Contactos: "+ Arrays.toString(contactos.toArray());
    }

    // Getter y Setter de contactos.
}

Hemos añadido la lista de contactos, además de un método toString() para facilitar sacar por pantalla la lista de contactos. La lista de contactos la hemos anotado con @Autowired. Esta anotación de Spring hace que todas las instancias de Persona que ha instanciado Spring se metan en esta lista.

Es importante notar que hemos dicho "todas las instancias de Persona que ha instanciado Spring". Es decir, si nosotros hacemos en nuestro código "new Persona(...)", esas instancias no irán a esta lista. Solo van si las ha creado Spring, bien en el fichero XML de configuración, bien con la anotación @Component.

Main de ejemplo[editar]

Ya solo nos queda hacer el main y ejecutarlo

public class ListaContactosMain {
    public static void main(String[] args) {
        BeanFactory factory = new FileSystemXmlApplicationContext("src/main/config/agenda-anotaciones-beans.xml");
        System.out.println(factory.getBean(ListaContactos.class));

    }
}

Nada nuevo, con FileSystemXmlApplicationContext le indicamos a Spring que arranque con ese fichero de configuración y nos devuelve la BeanFactory donde podemos pedir las instancias de las clases que ha instanciado Spring. Con factory.getBean() obtenemos la lista de contactos y la sacamos por pantalla gracias a su método toString().

Fíjate que a diferencia de ejemplos anteriores, aquí pedimos la instancia por su clase ListaContactos.class. En ejemplos anteriores lo pedíamos por el id que poníamos en el fichero Spring XML. Aquí no hemos indicado un id para ListaContactos, así que lo pedimos por clase.

Spring, en este sentido, hace siempre su mejor esfuerzo por encontrar lo que queremos. Por ejemplo, Spring pone también un nombre a los beans si no se lo indicamos nosotros. Ese nombre por defecto será el nombre de la clase, pero empezando en minúscula. El ejemplo funcionaría igual si lo pedimos por nombre, aunque no lo hayamos puesto

System.out.println(factory.getBean("listaContactos"));

Otras anotaciones y personalización[editar]

Veamos más detalles de todo esto.

Poner nombre el bean[editar]

Si queremos poner un nombre, basta añadirlo en la anotación @Component

@Component("pepe")
public class ListaContactos {

Este bean se llamará "pepe" para Spring y así podemos pedírselo a la factoría de Spring

System.out.println(factory.getBean("pepe"));

Seleccionar dependencias de @Autowired[editar]

Con solo la anotación @Autowired es obligatorio que haya al menos una instancia de lo que pidamos. Y debemos ser consecuentes en donde lo metemos. Por ejemplo,

@Autowired
Persona unaPersona;

Aquí es obligatorio que haya una instancia de Persona y sólo una. Si no hay ninguna instancia o hay más de una, tendremos un error y Spring abortará el arranque.

Si ponemos una List<Persona>, como hemos hecho en nuestro ejemplo, puede haber una o más instancias. Se meterán todas en la lista. Pero si no hay ninguna, tendremos un error.

Si queremos que no sea obligatorio al menos una instancia, podemos poner @Autowired(required=false). Con esto no habrá errores si no hay ninguna instancia.

Anotación @Qualifier[editar]

Si queremos que nos inyecten las instancias de nombres concretos, podemos añadir esta anotación junto con la @Autowired. Si miras las Persona que hemos instanciado en Spring, verás que hay una cuyo id es "Pedro". Podemos entonces hacer lo siguiente

    @Autowired
    @Qualifier("Pedro")
    private List<Persona> contactos;

y en la lista sólo se inyectará la instancia de Persona cuyo id es "Pedro"

Otras anotaciones @Service y @Repository[editar]

Con una función igual a @Component, Spring nos ofrece las anotaciones @Service y @Repository. Las tres hacen exactamente lo mismo y a nivel funcional no hay diferencia entre ellas. Spring pone estas tres opciones para que podamos dejar más claro nuestro código.

  • @Component es la anotación genérica y la usaremos cuando no nos cuadre más una de las otras dos.
  • @Service se suele utilizar en la capa de servicios de nuestra aplicación. Es decir, sirve para que Spring instancie clases que en nuestra aplicación ofrecen servicios de cualquier tipo.
  • <coede>@Repository se suele utilizar para acceso a base de datos. Es decir, con esta anotación instanciamos clases que sirve para acceder a base de datos.

Lo dicho, son intercambiables y funcionan exactamente igual. El usar una u otra depende sólo de nosotros y de lo claro que queramos dejar el código.