Skip to content

5. [Cours]: Introducción al framework Spring

Palabras clave: arquitectura multicapa, Spring, inyección de dependencias.

Spring apareció en 2004, inicialmente como contenedor de objetos. Desde entonces, ha evolucionado en múltiples ramificaciones: Spring MVC, Spring Data, Spring Batch, ... [http://spring.io]. En este capítulo solo presentaremos el contenedor de objetos. A continuación se indican algunos puntos de referencia:

  • Una aplicación tiene múltiples clases y algunas de ellas comparten objetos que deben ser únicos (singletons). Spring crea y gestiona estos singletons;
  • Spring coloca estos singletons en una estructura denominada «contexto»;
  • las clases acceden a los singletons de la aplicación solicitándolos a Spring mediante su nombre, su tipo o ambos;
  • Spring crea los singletons y gestiona sus posibles dependencias: de hecho, un singleton puede tener referencias a uno o varios singletons más. Cuando Spring crea un singleton, también crea sus dependencias;
  • cuando se inicia una aplicación basada en Spring, esta puede solicitar a Spring que cree todos los singletons de la aplicación. Estos estarán disponibles a continuación en el contexto de Spring;
  • Spring facilita el uso de arquitecturas por capas y la programación mediante interfaces. En los casos sencillos, cada capa está implementada por un singleton y implementa una interfaz. Si la aplicación trabaja con las interfaces de las capas y no con sus clases de implementación, se obtiene una arquitectura escalable que permite cambiar la implementación de una capa sin modificar las demás gracias a las dos características siguientes:
    • la aplicación obtiene una referencia a la capa a través de su nombre. Spring le proporciona una referencia a la clase que implementa la capa;
    • la aplicación utiliza esta referencia como si fuera la de la interfaz de la capa y no como la de una clase;

La declaración de los singletons puede realizarse de tres formas que pueden combinarse:

  • dentro de un archivo XML,
  • en una clase especial de configuración;
  • con cualquier clase mediante anotaciones;

A continuación presentamos tres ejemplos de configuración:

  • [exemple-01]: configuración centralizada en un único archivo XML;
  • [exemple-02]: configuración centralizada en una única clase Java;
  • [exemple-03]: configuración distribuida en varias clases Java;

El último ejemplo, [exemple-04], se centra en la configuración de Spring para una arquitectura por capas. Es el ejemplo más importante. Se utilizará constantemente para configurar las arquitecturas del documento.

Estos cuatro ejemplos sientan las bases de lo que viene a continuación:

  • configuración de Spring e inyección de dependencias;
  • uso de Maven para gestionar las dependencias de un proyecto;
  • uso de JUnit para probar los proyectos;

5.1. Support

 

La carpeta [support / chap-05] contiene los proyectos de Eclipse de este capítulo.

5.2. Exemple-01

5.2.1. El proyecto de Eclipse

 

5.2.2. La clase [Personne]

 

package istia.st.spring.core;

public class Personne {

    // campos
    private String nom;
    private String prenom;
    private int age;

    // constructores
    public Personne() {

    }

    public Personne(String nom, String prénom, int âge) {
        this.nom = nom;
        this.prenom = prénom;
        this.age = âge;
    }

    // toString
    public String toString() {
        return String.format("Personne[%s, %s,%d]", prenom, nom, age);
    }

    // getters y setters

    public String getNom() {
        return nom;
    }

    public void setNom(String nom) {
        this.nom = nom;
    }

    public String getPrenom() {
        return prenom;
    }

    public void setPrenom(String prenom) {
        this.prenom = prenom;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

}

Nota: los métodos getter y setter se pueden generar automáticamente de la siguiente manera [1-2]:

5.2.3. La clase [Appartement]

 

package istia.st.spring.core;

public class Appartement {

    // campos
    private Personne proprietaire;
    private int surface;

    // getters y setters

    public Personne getProprietaire() {
        return proprietaire;
    }

    public void setProprietaire(Personne proprietaire) {
        this.proprietaire = proprietaire;
    }

    public int getSurface() {
        return surface;
    }

    public void setSurface(int surface) {
        this.surface = surface;
    }

    // toString
    public String toString() {
        return String.format("Appartement[%s, %s]", proprietaire, surface);
    }

}

Nota: esta clase no tiene un constructor explícito. En este caso, siempre existe por defecto el constructor sin parámetros, que no realiza ninguna acción. Cuando se crean constructores, este constructor por defecto deja de existir implícitamente. Por lo tanto, hay que definirlo explícitamente:

public Appartement(){
}

5.2.4. El archivo 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:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util-4.0.xsd">
    <!-- Persona 01 -->
    <bean id="personne_01" class="istia.st.spring.core.Personne">
        <constructor-arg index="0" value="dubois" />
        <constructor-arg index="1" value="paul" />
        <constructor-arg index="2" value="34" />
    </bean>
    <!-- Persona 02 -->
    <bean id="personne_02" class="istia.st.spring.core.Personne">
        <property name="nom" value="martin" />
        <property name="prenom" value="micheline" />
        <property name="age" value="18" />
    </bean>
    <!-- una lista de personas -->
    <util:list id="club">
        <ref bean="personne_01" />
        <ref bean="personne_02" />
    </util:list>
    <!-- un piso -->
    <bean id="appartement" class="istia.st.spring.core.Appartement">
        <property name="surface" value="100" />
        <property name="proprietaire" ref="personne_01" />
    </bean>
</beans>
  • líneas 2 y 27: los singletons se definen dentro de una etiqueta <beans>;
  • líneas 6-10: cada singleton se define mediante una etiqueta <bean>;
  • línea 6: [id] es el identificador del singleton. [class] es el nombre completo de la clase que se va a instanciar;
  • líneas 7-9: los tres valores que se deben pasar al constructor de la clase [Personne];
  • líneas 12-16: la clase [Personne] se crea primero con su constructor por defecto [new Personne()]. A continuación, para cada etiqueta [property], se utiliza un método setter de la clase. Por ejemplo, en la línea 13, se ejecutará el método [setNom("martin")]. Por lo tanto, es necesario que exista el método [setNom]. Es importante tener esto en cuenta;
  • líneas 18-21: la etiqueta <util:list> permite definir un singleton que es una lista;
  • línea 19: hace referencia al singleton [personne_01] definido en la línea 6. Esto es lo que se conoce como inyección de dependencias. Se pueden utilizar dos atributos para inicializar el campo de un singleton:
    • [value]: para asignar al campo un valor primitivo (cadena, número, fecha, etc.),
    • [ref]: para asignar al campo la referencia de un objeto Spring;

Nota: el archivo de configuración de Spring se puede generar de la siguiente manera [1-4]:

5.2.5. La clase ejecutable

 

package istia.st.spring.core;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Demo01 {

    @SuppressWarnings({ "unchecked", "resource" })
    public static void main(String[] args) {
        // Recuperación del contexto de Spring
        ApplicationContext ctx = new ClassPathXmlApplicationContext("config-01.xml");
        // se recuperan los beans
        Personne p01 = ctx.getBean("personne_01", Personne.class);
        Personne p02 = ctx.getBean("personne_02", Personne.class);
        List<Personne> club = ctx.getBean("club", new ArrayList<Personne>().getClass());
        Appartement appart01 = ctx.getBean(Appartement.class);
        // se muestran
        System.out.println("personnes--------");
        System.out.println(p01);
        System.out.println(p02);
        System.out.println("club--------");
        for (Personne p : club) {
            System.out.println(p);
        }
        System.out.println("appartement--------");
        System.out.println(appart01);
        // los beans recuperados son singletons
        // se pueden solicitar varias veces, siempre se recupera el mismo bean
        Personne p01b = ctx.getBean("personne_01", Personne.class);
        System.out.println(String.format("beans [p01,p01b] identiques ? %s", p01b == p01));
    }
}
  • línea 14: crea el contexto de Spring. A continuación, se instancian todos los singletons definidos en el archivo [config-01.xml];
  • línea 16: solicita una referencia sobre el singleton identificado por [personne_01], de tipo [Personne]. Este segundo parámetro es opcional, pero en ese caso se recibe una referencia de tipo [Object], referencia que hay que convertir al tipo [Personne];
  • línea 19: no se utiliza el nombre del bean, sino únicamente su tipo, ya que solo hay un singleton de tipo [Appartement];
  • línea 18: se ha utilizado tanto el identificador como el tipo del singleton deseado. El identificador es superfluo, ya que solo hay un singleton de tipo [new ArrayList<Personne>().getClass()];
  • líneas 32-33: muestran que, si se solicita varias veces el mismo singleton, siempre se obtiene la misma referencia, lo que demuestra que se trata efectivamente de un singleton. Es importante comprender este punto;

Nota: se puede generar una clase ejecutable de la siguiente manera [1-6]:

  • Al marcar [6], la clase generada contendrá un método estático [main] que la hará ejecutable;

5.2.6. Las dependencias del proyecto

 
  • Dependencias de Spring: [spring-core, spring-beans, spring-context, spring-expression, commons-logging];

Las dependencias se añaden al proyecto de la siguiente manera:

  • en [1]: clic con el botón derecho del ratón sobre el proyecto / [Build Path] / [Configure Build Path];
  • en [2]: [Add JARs] si los archivos JARs que se van a añadir se encuentran en una carpeta del proyecto. De lo contrario, [Add External JARs];
  • en [3], seleccione los JARs que desea añadir al ClassPath del proyecto (en este caso, se encuentran en la carpeta [lib] dentro del proyecto);

Definición: el [ClassPath] de un proyecto es el conjunto de carpetas exploradas por el JVM (Java Virtual Machine) que ejecuta el proyecto, con el fin de encontrar una clase a la que este hace referencia. En un proyecto de Eclipse, el [ClassPath] está formado por los siguientes elementos:

  • la carpeta [bin] del proyecto;
  • los elementos del [Build Path] del proyecto;

La carpeta [bin] es la carpeta generada al compilar la carpeta [src]. Por lo tanto, todo lo que se coloque en la carpeta [src] pasa a formar parte automáticamente de la carpeta [ClassPath] (aunque no sea un archivo .java). Por lo tanto, en el proyecto anterior, el archivo de configuración de Spring [config-01.xml], que se encuentra en la carpeta [src], formará parte de la carpeta [Classpath] del proyecto en el momento de la ejecución.

5.2.7. Los resultados

févr. 21, 2014 1:16:23 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
Infos: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@3ac67f69: startup date [Fri Feb 21 13:16:23 CET 2014]; root of context hierarchy
févr. 21, 2014 1:16:23 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
Infos: Loading XML bean definitions from class path resource [config-01.xml]
personnes--------
Personne[paul, dubois,34]
Personne[micheline, martin,18]
club--------
Personne[paul, dubois,34]
Personne[micheline, martin,18]
appartement--------
Appartement[Personne[paul, dubois,34], 100]
beans [p01,p01b] identiques ? true

5.3. Exemple-02

5.3.1. El proyecto Eclipse

 

5.3.2. La clase de configuración de Spring


package istia.st.spring.core;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {

    @Bean
    public Personne personne_01() {
        return new Personne("Paul", "Dubois", 34);
    }

    @Bean
    public Personne personne_02() {
        return new Personne("Martin", "Micheline", 18);
    }

    @Bean
    public List<Personne> club(Personne personne_01, Personne personne_02) {
        List<Personne> personnes = new ArrayList<Personne>();
        personnes.add(personne_01);
        personnes.add(personne_02);
        return personnes;
    }

    @Bean
    public Appartement appartement(Personne personne_01) {
        Appartement appartement = new Appartement();
        appartement.setSurface(200);
        appartement.setPropriétaire(personne_01);
        return appartement;
    }
}
  • línea 9: la anotación [@Configuration] es una anotación de Spring. Indica que la clase anotada define singletons. Estos se definen mediante la anotación [@Bean]. Spring ejecutará todos los métodos anotados con [@Bean]. Estos crean los singletons de la aplicación;
  • líneas 12-15: definen un singleton identificado por [personne_01], es decir, el nombre del método.
  • línea 23: los parámetros [personne_01, personne_02] llevan los nombres de los singletons. Spring los inicializará automáticamente con las referencias de dichos singletons. A esto se le denomina inyección de parámetros;

Esta forma de configurar los singletons es más explícita que la que utiliza el archivo XML. De hecho, estamos reproduciendo nosotros mismos lo que Spring hacía de forma implícita a partir del archivo XML.

5.3.3. La clase ejecutable

 

package istia.st.spring.core;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo02 {

    @SuppressWarnings({ "unchecked", "resource" })
    public static void main(String[] args) {
        // se recupera el contexto de Spring
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
        // se recuperan los beans
        Personne p01 = ctx.getBean("personne_01", Personne.class);
        Personne p02 = ctx.getBean("personne_02", Personne.class);
        List<Personne> club = ctx.getBean("club", new ArrayList<Personne>().getClass());
        Appartement appart01 = ctx.getBean(Appartement.class);
        // se muestran
        System.out.println("personnes--------");
        System.out.println(p01);
        System.out.println(p02);
        System.out.println("club--------");
        for (Personne p : club) {
            System.out.println(p);
        }
        System.out.println("appartement--------");
        System.out.println(appart01);
        // los beans recuperados son singletons
        // se pueden solicitar varias veces, siempre se recupera el mismo bean
        Personne p01b = ctx.getBean("personne_01", Personne.class);
        System.out.println(String.format("beans [p01,p01b] identiques ? %s", p01b == p01));
    }
}
  • la línea 13 provoca la instanciación de todos los beans definidos en la clase [Config];
  • el resto del código no cambia;

5.3.4. Las dependencias del proyecto

Las dependencias se establecen mediante el siguiente archivo [pom.xml]:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-02</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-core-02</name>
    <description>Introduction à Spring</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.7</java.version>
    </properties>

    <dependencies>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.1.3.RELEASE</version>
        </dependency>
    </dependencies>
    <!-- plugins -->
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>

La gestión manual de las dependencias de un proyecto se convierte en un quebradero de cabeza cuando se utilizan bibliotecas Java cuyas dependencias se desconocen. Por ejemplo, el framework [Hibernate], que gestiona el acceso a las bases de datos, tiene decenas de dependencias. El proyecto [Maven] resuelve este problema. Se indica el nombre de la dependencia que se necesita. Esta se busca automáticamente en los repositorios de Maven repartidos por Internet. Si la dependencia solicitada tiene a su vez otras dependencias, estas también se descargan automáticamente. Las dependencias descargadas se almacenan en un repositorio local en el equipo. Si más adelante otra aplicación necesita la misma dependencia, esta no se descargará, sino que se buscará en el repositorio local. Una dependencia se caracteriza por los siguientes elementos:

  • línea 17: una etiqueta <dependency>;
  • línea 18: un atributo [groupId] que, por lo general, identifica a la empresa que ha creado la dependencia;
  • línea 19: un atributo [artifactId] que identifica la dependencia;
  • línea 20: un atributo [version] que identifica la versión deseada;

La generación del proyecto producirá a su vez un componente Maven definido por las líneas 4-8:

  • líneas 4-6: los atributos [ groupId, artifactId,version] que acabamos de describir;
  • líneas 7-8: son atributos opcionales;

Más adelante volveremos sobre la función de las líneas 24-40. Para convertir un proyecto Eclipse normal en un proyecto Maven, hay que hacer dos cosas:

  • crear el archivo [pom.xml] anterior;
  • declarar que el proyecto es ahora un proyecto Maven [1-4]:

El icono de un proyecto Maven tiene una «M [4]». La «S» indica que el proyecto contiene elementos de Spring. No se recomienda convertir (como acabamos de hacer) un proyecto de Eclipse en un proyecto Maven, ya que, al hacerlo, el proyecto no tiene la estructura esperada para un proyecto Maven, lo que a veces puede provocar problemas inesperados.

5.3.5. Generación del artefacto Maven del proyecto

Denominamos «artefacto Maven del proyecto» al elemento definido en las líneas 4-6 del archivo [pom.xml]:


    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-02</artifactId>
<version>0.0.1-SNAPSHOT</version>

Para generar este artefacto, es necesario que las siguientes líneas 3-7 estén presentes en el archivo [pom.xml]:


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
</build>

Estas definen el plugin de Maven capaz de generar el artefacto del proyecto. A continuación, se procede de la siguiente manera:

El artefacto así generado se guarda en el repositorio local de Maven. La ubicación de este se puede encontrar en la configuración de Eclipse:

 

A continuación, es posible comprobar que el artefacto de Maven se ha instalado correctamente:

 

A partir de ahora, cualquier otro proyecto Maven local podrá utilizar este archivo.

5.4. Exemple-03

5.4.1. El proyecto Eclipse

En esta ocasión, creamos un proyecto Maven [1-8]:

  • en [3b]: selecciona una carpeta vacía donde se generará el proyecto;
  • en [4]: el identificador del grupo Maven al que pertenecerá el proyecto;
  • en [5]: el nombre del artefacto Maven generado;
  • en [6]: su versión;
  • en [7]: su modo de empaquetado (también existen war, ear, apk, etc.);
  • en [8]: el proyecto así creado;

Un proyecto de Maven tiene, por defecto, una estructura de directorios concreta:

  • [src / main / java]: el código fuente del proyecto. Los archivos compilados a partir de este código fuente se guardarán en la carpeta [target/classes] del proyecto;
  • [src / main / resources]: los recursos que deben estar en la ruta de clases (Classpath) del proyecto sin ser necesariamente código fuente Java. Se copiarán tal cual en la carpeta [target/classes] del proyecto;
  • [src / test / java]: los códigos fuente de las pruebas del proyecto. Los resultados de la compilación de estos códigos se guardarán en la carpeta [target/test-classes] del proyecto. Estos elementos no se incluyen en el archivo Maven del proyecto;
  • [src / test / resources]: los recursos que deben estar en la ruta de clases (Classpath) del proyecto para las pruebas, sin ser por ello código fuente Java. Se copiarán tal cual en la carpeta [target/test-classes] del proyecto;

Completamos el proyecto de la siguiente manera:

 

5.4.2. La configuración de Maven

De forma predeterminada, se genera un archivo [pom.xml]. Lo modificamos de la siguiente manera:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-03</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-core-03</name>
    <description>Introduction à Spring</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <!-- proyecto padre de Maven -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <dependencies>
        <!-- Contexto de Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <!-- registros -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>

    </dependencies>

    <!-- complementos -->
    <build>
        <plugins>
            <!-- para generar el archivo del proyecto con sus dependencias -->
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <!-- para la instalación del artefacto del proyecto en el repositorio local de Maven -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • línea 11: el proyecto está codificado en UTF-8;
  • línea 12: se utiliza un JDK 1.8 para compilar el proyecto;
  • líneas 16-20: para los proyectos que utilizan las bibliotecas de Spring, resulta práctico utilizar un proyecto Maven principal denominado [spring-boot-starter-parent]. Este define las versiones de las diferentes bibliotecas de Spring, así como las de sus dependencias. De este modo, ya no es necesario definirlas en la definición de dependencias. Por lo tanto, en las líneas 24-27 no se especifica la versión deseada de [spring-context]. Será la definida por el proyecto padre [spring-boot-starter-parent]. Esta técnica permite no tener que preocuparse por posibles incompatibilidades de versiones entre dependencias. Las definidas por el proyecto padre son compatibles entre sí;
  • líneas 29-32: Spring escribe una gran cantidad de información en la consola a través de una biblioteca de registros. Esta se importa aquí;
  • líneas 40-47: un plugin de Maven sobre el que volveremos más adelante;
  • líneas 50-52: el complemento para generar el artefacto Maven del proyecto;

5.4.3. La clase de configuración de Spring

  

La clase [Config] es la siguiente:


package istia.st.spring.core.config;

import istia.st.spring.core.entities.Personne;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan({ "spring.core.entities" })
public class Config {

    @Bean
    public Personne personne_01() {
        return new Personne("Paul", "Dubois", 34);
    }

    @Bean
    public Personne personne_02() {
        return new Personne("Martin", "Micheline", 18);
    }

    @Bean
    public List<Personne> club(Personne personne_01, Personne personne_02) {
        List<Personne> personnes = new ArrayList<Personne>();
        personnes.add(personne_01);
        personnes.add(personne_02);
        return personnes;
    }

    @Bean
    public int mySurface() {
        return 200;
    }
}
  • Aquí encontramos un código ya comentado con dos novedades:
    • línea 13: indica que hay otros beans que deben instanciarse en el paquete [spring.core.entities],
    • líneas 34-37: un bean [mySurface];

5.4.4. La clase [Appartement]

 

package spring.core.entities;

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

@Component
public class Appartement {

    // campos inyectados por Spring
    @Autowired
    @Qualifier("personne_01")
    private Personne propriétaire;

    @Autowired
    @Qualifier("mySurface")
    private int surface;

    // getters y setters
    public Personne getPropriétaire() {
        return propriétaire;
    }

    public void setPropriétaire(Personne propriétaire) {
        this.propriétaire = propriétaire;
    }

    public int getSurface() {
        return surface;
    }

    public void setSurface(int surface) {
        this.surface = surface;
    }

    // toString
    public String toString() {
        return String.format("Appartement[%s, %s]", propriétaire, surface);
    }

}
  • línea 7: la anotación [@Component] indica a Spring que la clase es un singleton que el framework debe instanciar y gestionar. Esto se debe a que, en la clase [Config], hemos escrito [@ComponentScan({ "istia.st.spring.core.entities" })], por lo que se encontrará este singleton;
  • línea 11: solicita a Spring que inyecte en el campo la referencia de uno de los singletons. Esta puede definirse de dos maneras:
    • por su identificador (líneas 12, 16),
    • por su tipo, si solo hay un singleton de ese tipo;

5.4.5. Ejecución del proyecto

La ejecución del proyecto da el siguiente resultado en la consola:

17:32:39.797 [main] DEBUG o.s.core.env.StandardEnvironment - Adding [systemProperties] PropertySource with lowest search precedence
....
17:32:40.134 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'appartement'
personnes--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
club--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
appartement--------
Appartement[Personne[Dubois, Paul,34], 200]
17:32:40.135 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'personne_01'
beans [p01,p01b] identiques ? true
  • líneas 1-3: Spring genera una gran cantidad de registros, varias decenas de líneas. Estos registros pueden resultar muy útiles para depurar un proyecto que no funciona. Cuando funciona, se pueden reducir los registros de la siguiente manera:
  

En la carpeta [src / main / resources] se crea el siguiente archivo [logback.xml]:


<configuration> 

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
    <!-- a los codificadores se les asigna por defecto el tipo
         ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- control del nivel de los registros -->
  <root level="info"> <!-- info, debug, warn -->
    <appender-ref ref="STDOUT" />
  </root>
</configuration>
  • En la línea 12, se establece el nivel de los registros. [debug] es un nivel muy detallado, mientras que [info] lo es mucho menos;

Estos son los resultados con [level=info]:

17:39:58.580 [main] INFO  o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7cf10a6f: startup date [Tue Apr 07 17:39:58 CEST 2015]; root of context hierarchy
personnes--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
club--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
appartement--------
Appartement[Personne[Dubois, Paul,34], 200]
beans [p01,p01b] identiques ? true

Ahora solo hay una línea de registros.

5.4.6. Generación del archivo del proyecto con sus dependencias

El archivo creado en el proyecto anterior también puede utilizarse en un proyecto de Eclipse que no sea de Maven. Algunos proyectos utilizan numerosas bibliotecas y puede resultar complicado no olvidarse de ninguna. Ahí es donde Maven hace maravillas, ya que basta con nombrar la dependencia de nivel más alto para que las demás, de nivel inferior, se añadan automáticamente a la ruta de clases (Classpath) del proyecto. Cuando un proyecto de Eclipse que no utiliza Maven debe emplear los archivos de un proyecto Maven, es posible generar el artefacto de este último con todas sus dependencias (lo cual no ocurría en el proyecto anterior). Para esta generación, es necesario que las siguientes líneas 3-10 estén presentes en el archivo [pom.xml]:


    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
</build>

Estas líneas definen el plugin de Maven capaz de generar el artefacto del proyecto con sus dependencias. A continuación, se procede de la siguiente manera [1-6]:

  • [4-6] representan una configuración de ejecución de Maven;
  • en [4], introduce cualquier nombre;
  • en [5], indica la carpeta del proyecto;
  • en [6], introduce los objetivos de Maven (goals):
    • [clean]: se elimina la carpeta [target] del proyecto;
    • [compile]: se compila el proyecto. Los resultados de la compilación se colocan en una carpeta [target] regenerada;
    • [assembly:single]: las clases del proyecto y sus dependencias se colocan en un único archivo jar dentro de la carpeta [target];

Tras la ejecución, se obtiene el siguiente resultado:

Un archivo JAR es un archivo comprimido que, por lo tanto, se puede abrir con un programa de descompresión. Una vez descomprimido el archivo anterior, se obtiene la siguiente estructura de directorios:

  • en [8], las clases de las dependencias del proyecto;
  • en [9], las clases del propio proyecto;

5.5. Exemple-04

5.5.1. Objetivo

Este ejemplo retoma uno de los presentados en el documento [Introduction à Spring IoC], en el que se muestra la aportación de Spring a la configuración de arquitecturas multicapa. En el documento original, el ejemplo se trata con una configuración de Spring realizada mediante un archivo XML. Aquí tratamos el ejemplo con una configuración mediante clases Java y anotaciones.

Lo que queremos hacer aquí es configurar un proyecto de Spring para la siguiente arquitectura:

Cada capa presenta una interfaz implementada con dos clases. Queremos demostrar que, gracias a Spring, se puede cambiar la implementación de una capa sin que ello afecte en absoluto al código de las demás capas.

5.5.2. El proyecto de Eclipse

5.5.2.1. Génération

Creamos un nuevo tipo de proyecto:

  • en [4], introduce el nombre del proyecto Eclipse;
  • en [5], elige un proyecto Maven;
  • en [6], elige una versión de Java >=1.7;
  • en [7], elige la versión de Spring Boot propuesta;
  • la información de [8-11] corresponde a Maven;
  • en [12], se puede seleccionar una o varias de las dependencias propuestas. Esto tendrá como efecto la integración de una serie de dependencias en el archivo [pom.xml] de Maven;
  • en [13], se debe indicar una carpeta existente y vacía para alojar el proyecto;
  • en [14], el proyecto generado. Vamos a analizar sus elementos;

El proyecto es un proyecto Maven configurado mediante el siguiente archivo [pom.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-04</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring-core-04</name>
    <description>Programmation par interfaces</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
        <relativePath/> <!-- búsqueda del padre en el repositorio -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>demo.SpringCore04Application</start-class>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  • líneas 6-12: recogen la información introducida en el asistente de creación del proyecto;
  • líneas 14-19: el proyecto Maven principal, que define una serie de bibliotecas con sus respectivas versiones. Si alguna de ellas es una dependencia del proyecto, se menciona en el archivo [pom.xml] sin indicar su versión;
  • línea 23: esta línea solo es necesaria si se pretende generar un archivo ejecutable del proyecto. De lo contrario, no se utiliza;
  • líneas 28-31: la dependencia mínima de un proyecto Spring Boot. Recordemos que no hemos seleccionado ninguna dependencia en la lista de selección;
  • líneas 33-37: la dependencia necesaria para gestionar las pruebas unitarias JUnit y [http://junit.org/] integradas con Spring. La línea 36 indica que la dependencia solo es necesaria para las pruebas. Por lo tanto, no se incluirá en el archivo del proyecto;
  • líneas 42-45: el complemento que permite generar el artefacto Maven del proyecto;

La lista de dependencias que aporta este archivo es la siguiente: [1]:

Veremos que son suficientes para lo que queremos hacer aquí.

5.5.2.2. La clase ejecutable

  

La clase ejecutable [SpringCore04Application] [[2] es la siguiente:


package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringCore04Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringCore04Application.class, args);
    }
}
  • En la línea 6, la anotación [@SpringBootApplication] es un atajo para las tres anotaciones [@Configuration, @EnableAutoConfiguration, @ComponentScan], lo que significa que:
    • que la clase [SpringCore04Application] es una clase de configuración de Spring;
    • que se le pide a Spring Boot que realice configuraciones a partir de las clases que encuentre en la ruta de clases del proyecto, es decir, en este caso, en las dependencias de Maven;
    • que examine la carpeta actual (la de la clase [SpringCore04Application]) para encontrar en ella otros posibles componentes de Spring;
  • Línea 10: se ejecuta el método estático [SpringApplication.run]. Su primer parámetro es una clase de configuración de Spring, en este caso la clase [SpringCore04Application]. Su segundo parámetro es, en este caso, la lista de argumentos pasados al método [main] (línea 9). El método estático [SpringApplication.run] se encarga de crear el contexto de Spring, es decir, de crear los distintos beans que se encuentran bien en las clases de configuración, bien en las carpetas exploradas por la anotación [@ComponentScan]. El método [main] aquí no hace nada más. Para darle un poco más de sustancia, lo vamos a transformar de la siguiente manera:

package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class SpringCore04Application {

    public static void main(String[] args) {
        // instanciación del contexto de Spring
        ConfigurableApplicationContext context = SpringApplication.run(SpringCore04Application.class, args);
        // visualización del contexto
        System.out.println("---------------- Liste des beans Spring");
        for (String beanName : context.getBeanDefinitionNames()) {
            System.out.println(beanName);
        }
        // cierre del contexto
        context.close();
    }
}
  • línea 12: el método estático [SpringApplication.run] devuelve el contexto de Spring que ha creado;
  • líneas 15-17: se muestra el nombre de todos los beans de este contexto;

Podemos ejecutar la aplicación de la siguiente manera: [1-3]. El método habitual [Run As Java Application] también es válido.

Se obtiene el siguiente resultado:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.3.RELEASE)

2015-04-08 11:18:38.254  INFO 4796 --- [           main] demo.SpringCore04Application             : Starting SpringCore04Application on Gportpers3 with PID 4796 (D:\data\istia-1415\polys\istia\dvp-spring-database\codes\original\intro-spring-core\spring-core-04\target\classes started by ST in D:\data\istia-1415\polys\istia\dvp-spring-database\codes\original\intro-spring-core\spring-core-04)
2015-04-08 11:18:38.295  INFO 4796 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@64485a47: startup date [Wed Apr 08 11:18:38 CEST 2015]; root of context hierarchy
2015-04-08 11:18:38.776  INFO 4796 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2015-04-08 11:18:38.788  INFO 4796 --- [           main] demo.SpringCore04Application             : Started SpringCore04Application in 0.773 seconds (JVM running for 1.335)
---------------- Liste des beans Spring
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
springCore04Application
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor.enhancedConfigurationProcessor
org.springframework.boot.autoconfigure.AutoConfigurationPackages
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.condition.BeanTypeRegistry
propertySourcesPlaceholderConfigurer
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
mbeanExporter
objectNamingStrategy
mbeanServer
2015-04-08 11:18:38.789  INFO 4796 --- [           main] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@64485a47: startup date [Wed Apr 08 11:18:38 CEST 2015]; root of context hierarchy
2015-04-08 11:18:38.790  INFO 4796 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown
  • líneas 14-28: los beans del contexto de Spring. Desconocemos su función. Encontramos el bean [springCore04Application] en la línea 18, que, debido a su anotación [@SpringBootApplication], se convierte automáticamente en un bean de Spring;
  • las demás líneas son registros de Spring de nivel [INFO]. Como ya hemos visto, estos registros pueden controlarse mediante el archivo [logback.xml] ubicado en la ruta de clases (Classpath) del proyecto:
  

<configuration> 

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
    <!-- A los codificadores se les asigna por defecto el tipo
         ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- control del nivel de los registros -->
  <root level="warn"> <!-- info, debug, warn -->
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

Si en la línea 12 anterior sustituimos el nivel [info] por [warn], obtenemos el siguiente resultado:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.3.RELEASE)

---------------- Liste des beans Spring
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
springCore04Application
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor.enhancedConfigurationProcessor
org.springframework.boot.autoconfigure.AutoConfigurationPackages
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.condition.BeanTypeRegistry
propertySourcesPlaceholderConfigurer
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
mbeanExporter
objectNamingStrategy
mbeanServer

Los registros han desaparecido. Solo aparecen los mensajes de nivel [warn] y aquí no ha habido ninguno.

5.5.3. Implementación de las diferentes capas de la arquitectura

Ahora vamos a implementar las tres capas de la arquitectura anterior:

  

La capa [DAO] se implementa mediante el paquete [spring.core.dao]. Presenta la siguiente interfaz [IDao]:


package spring.core.dao;

public interface IDao {

    public int doSomethingInDaoLayer(int a, int b);
}

Esta interfaz tiene dos implementaciones: [Dao1] y [Dao2]:


package spring.core.dao;

public class Dao1 implements IDao {

    public int doSomethingInDaoLayer(int a, int b) {
        return a+b;
    }

}

package spring.core.dao;

public class Dao2 implements IDao {

    public int doSomethingInDaoLayer(int a, int b) {
        return a-b;
    }

}

La capa [métier] está implementada por el paquete [spring.core.metier]. Presenta la siguiente interfaz [IMetier]:


package spring.core.metier;

public interface IMetier {

    public int doSomethingInMetierLayer(int a, int b);
}

Esta interfaz tiene dos implementaciones: [Metier1] y [Metier2]:


package spring.core.metier;

import spring.core.dao.IDao;

public class Metier1 implements IMetier {

    private IDao dao;

    public int doSomethingInMetierLayer(int a, int b) {
        a++;
        b++;
        return dao.doSomethingInDaoLayer(a, b);
    }

    public void setDao(IDao dao) {
        this.dao = dao;
    }
}

package spring.core.metier;

import spring.core.dao.IDao;

public class Metier2 implements IMetier {

    private IDao dao;

    public int doSomethingInMetierLayer(int a, int b) {
        a--;
        b++;
        return dao.doSomethingInDaoLayer(a, b);
    }

    public void setDao(IDao dao) {
        this.dao = dao;
    }


}

La capa [UI] está implementada por el paquete [spring.core.ui]. Presenta la siguiente interfaz [IUi]:


package spring.core.ui;

public interface IUi {

    public int doSomethingInUiLayer(int a, int b);
}

Esta interfaz tiene dos implementaciones: [Ui1] y [Ui2]:


package spring.core.ui;

import spring.core.metier.IMetier;

public class Ui1 implements IUi {

    private IMetier metier;
    
    public int doSomethingInUiLayer(int a, int b) {
        a++;
        b++;
        return metier.doSomethingInMetierLayer(a, b);
    }

    public void setMetier(IMetier metier) {
        this.metier = metier;
    }

}

package spring.core.ui;

import spring.core.metier.IMetier;

public class Ui2 implements IUi {

    private IMetier metier;

    public int doSomethingInUiLayer(int a, int b) {
        a--;
        b++;
        return metier.doSomethingInMetierLayer(a, b);
    }

    public void setMetier(IMetier metier) {
        this.metier = metier;
    }

}

5.5.4. Configuración del proyecto Spring

  

La clase de configuración [Config] es la siguiente:


package spring.core.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import spring.core.dao.Dao1;
import spring.core.dao.Dao2;
import spring.core.dao.IDao;
import spring.core.metier.IMetier;
import spring.core.metier.Metier1;
import spring.core.metier.Metier2;
import spring.core.ui.IUi;
import spring.core.ui.Ui1;
import spring.core.ui.Ui2;

@Configuration
public class Config {

    // -------------- implementación [Ui1, Metier1, Dao1]
    @Bean
    public IDao dao1() {
        return new Dao1();
    }

    @Bean
    public IMetier metier1(IDao dao1) {
        Metier1 metier = new Metier1();
        metier.setDao(dao1);
        return metier;
    }

    @Bean
    public IUi ui1(IMetier metier1) {
        Ui1 ui = new Ui1();
        ui.setMetier(metier1);
        return ui;
    }

    // -------------- implementación [Ui2, Metier2, Dao2]
    @Bean
    public IDao dao2() {
        return new Dao2();
    }

    @Bean
    public IMetier metier2(IDao dao2) {
        Metier2 metier = new Metier2();
        metier.setDao(dao2);
        return metier;
    }

    @Bean
    public IUi ui2(IMetier metier2) {
        Ui2 ui = new Ui2();
        ui.setMetier(metier2);
        return ui;
    }
}
  • líneas 20-23: el bean denominado [dao1] (nombre del método) es una instancia de la clase [Dao1] (línea 22), considerada como una implementación de la interfaz [IDao] (línea 21). Por lo tanto, el bean [dao1] se considera una instancia de interfaz (la terminología es incorrecta, pero se puede entender) y no una instancia de clase. Es importante comprender este punto. Todos los demás beans también serán instancias de interfaces;
  • líneas 25-30: una instancia de la interfaz [IMetier] implementada por la clase [Metier1];
  • líneas 32-37: una instancia de la interfaz [IUi] implementada por la clase [Ui1];
  • líneas 20-37: implementan las capas [UI, Metier, DAO] con instancias de [Ui1, Metier1, Dao1];
  • líneas 40-57: implementan las capas [UI, Metier, DAO] con instancias de [Ui2, Metier2, Dao2];

5.5.5. Prueba unitaria [JUnitTest]

  

La clase [JUnitTest] se incluye en la rama [src / test / java] del proyecto Maven. Los elementos de esta rama no se incluyen en el archivo final del proyecto. Su código es el siguiente:


package spring.core.tests;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import spring.core.config.Config;
import spring.core.dao.IDao;
import spring.core.metier.IMetier;
import spring.core.ui.IUi;

@SpringApplicationConfiguration(classes = { Config.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTest {
...
}
  • línea 16: la anotación [@SpringApplicationConfiguration] es una anotación del proyecto Spring Boot Test (línea 8). Se introduce a través de la siguiente dependencia del archivo [pom.xml]:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
</dependency>

Esta anotación tiene como parámetro la lista de clases de configuración que se deben utilizar para construir el contexto Spring necesario para la prueba. En este caso, utilizamos la clase de configuración [Config], ya presentada anteriormente;

  • línea 17: la anotación [@RunWith] es una anotación JUnit (línea 5). Su parámetro es la clase encargada de ejecutar las pruebas en lugar de la clase predeterminada del framework JUnit. Esta clase es una clase de Spring (línea 9). Utilizará las anotaciones de Spring presentes en la clase de prueba;

La clase completa es la siguiente


...

@SpringApplicationConfiguration(classes = { Config.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTest {

    // capa [UI]
    @Autowired
    @Qualifier("ui1")
    private IUi ui1;

    @Autowired
    @Qualifier("ui2")
    private IUi ui2;

    // capa [métier]
    @Autowired
    @Qualifier("metier1")
    private IMetier metier1;

    @Autowired
    @Qualifier("metier2")
    private IMetier metier2;

    // capa [dao]
    @Autowired
    @Qualifier("dao1")
    private IDao dao1;

    @Autowired
    @Qualifier("dao2")
    private IDao dao2;

    @Test
    public void testDao() {
        Assert.assertEquals(30, dao1.doSomethingInDaoLayer(10, 20));
        Assert.assertEquals(-10, dao2.doSomethingInDaoLayer(10, 20));
    }

    @Test
    public void testMetier() {
        Assert.assertEquals(32, metier1.doSomethingInMetierLayer(10, 20));
        Assert.assertEquals(-12, metier2.doSomethingInMetierLayer(10, 20));
    }

    @Test
    public void testUI() {
        Assert.assertEquals(34, ui1.doSomethingInUiLayer(10, 20));
        Assert.assertEquals(-14, ui2.doSomethingInUiLayer(10, 20));
    }

}
  • líneas 8-10: se inyecta (línea 8) el bean denominado (línea 9) [ui1]. Cabe destacar, en la línea 10, que se inyecta una instancia de interfaz y no una instancia de clase;
  • líneas 21-32: se inyectan de la misma forma los demás beans definidos en la clase [Config];
  • línea 34: la anotación [@Test] designa un método que debe ejecutarse durante las pruebas. Las demás anotaciones posibles son las siguientes:
    • [@BeforeClass]: método que se debe ejecutar antes de iniciar las pruebas;
    • [@AfterClass]: método que se debe ejecutar una vez finalizadas todas las pruebas;
    • [@Before]: método que debe ejecutarse antes de cada prueba;
    • [@After]: método que se debe ejecutar después de cada prueba;
  • línea 36: se comprueba que la llamada a [dao1.doSomethingInDaoLayer(10, 20)] devuelve efectivamente 30. Por convención, el primer parámetro es el valor esperado y el segundo, el valor real;
  • línea 36: comprueba la instancia [dao1] de la interfaz [IDao];
  • línea 37: comprueba la instancia [dao2] de la interfaz [IDao];
  • línea 42: comprueba la instancia [metier1] de la interfaz [IMetier];
  • línea 43: comprueba la instancia [metier2] de la interfaz [IMetier];
  • línea 48: comprueba la instancia [ui1] de la interfaz [IUi];
  • línea 36: comprueba la instancia [ui2] de la interfaz [IUi];

Las aserciones que se pueden utilizar en un método de prueba son las siguientes:

  • assertEquals(expresión1, expresión2): comprueba que los valores de ambas expresiones sean iguales. Se aceptan numerosos tipos de expresión (int, String, float, double, boolean, char, short). Si ambas expresiones no son iguales, se lanza una excepción de tipo [AssertionFailedError ],
  • assertEquals(real1, real2, delta): comprueba que dos valores reales sean iguales con una tolerancia de delta, es decir, c.a.d abs(real1 - real2) <= delta. Por ejemplo, se puede escribir assertEquals(real1, real2, 1E-6) para comprobar que dos valores son iguales con una precisión de 10⁻⁶,
  • assertEquals(mensaje, expresión1, expresión2) y assertEquals(mensaje, real1, real2, delta) son variantes que permiten especificar el mensaje de error que se asociará a la excepción de tipo [AssertionFailedError] lanzada cuando el método [assertEquals] falla,
  • assertNotNull(Object) y assertNotNull(message, Object): comprueba que la referencia Object no sea nula,
  • assertNull(Object) y assertNull(message, Object): comprueba que la referencia Object sea igual a null,
  • assertSame(Object1, Object2) y assertSame(mensaje, Object1, Object2): comprueba que las referencias Object1 y Object2 apunten al mismo objeto,
  • assertNotSame(Object1, Object2) y assertNotSame(mensaje, Object1, Object2): comprueba que las referencias Object1 y Object2 no apunten al mismo objeto;

Para ejecutar la prueba, se puede proceder de la siguiente manera:

Se obtiene el siguiente resultado:

 

En este caso, todas las pruebas han tenido éxito. ¿Qué nos muestra este ejemplo? Muestra la flexibilidad que aporta el framework Spring a la hora de configurar una arquitectura por capas. Se puede decidir utilizar la implementación [Ui1, Metier1, Dao1] o [Ui2, Metier2, Dao2] simplemente mediante la configuración. Así, en la prueba anterior JUnit, si solo se mantiene la inyección de los beans [ui1, metier1, dao1], se trabaja con la primera arquitectura. Para cambiar de arquitectura, basta con cambiar los beans inyectados. Esto se hace sin modificar el código de las capas que implementan las interfaces. A este tipo de programación se le denomina «programación por interfaces», ya que no se utilizan las instancias de las clases que implementan las capas, sino las instancias de sus interfaces.

5.6. Conclusion

  • Spring gestiona objetos que son singletons (una única instancia). Spring también gestiona objetos que se instancian cada vez que se solicita una instancia a Spring. Este caso también se presentará en este documento;
  • estos objetos pueden declararse de diversas formas que pueden combinarse:
    • en un archivo XML,
    • en una clase Java anotada con [@Configuration],
    • con cualquier clase Java anotada con [@Component, @Service, ...];
  • un objeto Spring puede inyectarse en otro objeto Spring con la anotación [@Autowired]. En este caso, se habla de inyección de dependencias (DI: Dependency Injection);
  • Spring resulta muy práctico para configurar arquitecturas por capas junto con el paradigma de la programación por interfaces;