Skip to content

15. Spring IoC

15.1. Introducción

Nos proponemos descubrir las posibilidades de configuración e integración del marco Spring (http://www.springframework.org), así como definir y utilizar el concepto de IoC (Inversión de control), también conocido como inyección de dependencias (Dependency Injection).

Consideremos la aplicación de tres capas que acabamos de crear:

Para responder a las solicitudes del usuario, el controlador [Application] debe dirigirse a la capa [service]. En nuestro ejemplo, esta era una instancia de tipo [DaoImpl]. El controlador [Application] obtuvo una referencia a la capa [service] en su método [init] (apartado 14.8.3):

@SuppressWarnings("serial")
public class Application extends HttpServlet {
...

    // servicio
    ServiceImpl service=null;
...
    // inicialización
    @SuppressWarnings("unchecked")
    public void init() throws ServletException {
...
        // instanciación de la capa [dao]
        DaoImpl dao = new DaoImpl();
        dao.init();
        // instanciación de la capa [service]
        service = new ServiceImpl();
        service.setDao(dao);
    }
  • línea 6: la capa [dao] se ha instanciado mediante la creación explícita de una instancia [DaoImpl]
  • línea 9: la capa [service] se ha instanciado mediante la creación explícita de una instancia [ServiceImpl]

Recordemos que las clases [DaoImpl] y [ServiceImpl] implementan interfaces, concretamente las interfaces [IDao] y [IService], respectivamente. En futuras versiones, la interfaz [IDao] será implementada por una clase que gestione una lista de personas almacenada en una base de datos. Llamemos a esta clase [DaoBD] a efectos del ejemplo. Sustituir la implementación [DaoImpl] de la capa [dao) por la implementación [DaoBD] requerirá una recompilación de la capa [web]. De hecho, la línea 6 anterior, que instancia la capa [dao] con un tipo [DaoImpl], ahora debe instanciarla con un tipo [DaoBD]. Por lo tanto, nuestra capa [web] depende de la capa [dao]. La línea 9 anterior muestra que también depende de la capa [service].

Spring IoC nos permitirá crear una aplicación de tres capas en la que las capas sean independientes entre sí, c.a.d, de modo que cambiar una no requiera cambiar las demás. Esto aporta una gran flexibilidad a la hora de desarrollar la aplicación.

La arquitectura anterior evolucionará de la siguiente manera:

Con [Spring IoC], el controlador [Application] obtendrá la referencia que necesita en la capa [service] de la siguiente manera:

  1. en su método [init], solicitará a la capa [Spring IoC] que le proporcione una referencia en la capa [service]
  2. A continuación, [Spring IoC] utilizará un archivo de configuración XML que le indica qué clase debe instanciarse y cómo debe inicializarse.
  3. [Spring IoC] devuelve al controlador [Application] la referencia de la capa [service] creada.

La ventaja de esta solución es que, a partir de ahora, los nombres de las clases que instancian las diferentes capas ya no están codificados de forma fija en el método [init] del controlador, sino que simplemente figuran en un archivo de configuración. Cambiar la implementación de una capa implicará un cambio en este archivo de configuración, pero no en el controlador.

Veamos ahora las posibilidades que ofrece [Spring IoC] con ayuda de algunos ejemplos.

15.2. Spring IoC en la práctica

15.2.1. Spring

[Spring IoC] forma parte de un proyecto más amplio disponible en la URL [http://www.springframework.org/] (mayo de 2006):

  • [1]: Spring utiliza diversas tecnologías de terceros denominadas aquí dépendances. Es necesario descargar la versión con dépendances para evitar tener que descargar posteriormente las bibliotecas de las herramientas de terceros.
  • [2]: la estructura de directorios del archivo comprimido descargado
  • [3]: la distribución [Spring], c.a.d. Los archivos .jar del propio proyecto Spring sin sus dependencias. El aspecto [IoC] de Spring viene dado por los archivos [spring-core.jar, spring-beans.jar].
  • [4,5]: los archivos de las herramientas de terceros

15.2.2. Proyectos Eclipse de los ejemplos

Vamos a crear tres ejemplos que ilustren el uso de Spring IoC. Todos ellos se incluirán en el siguiente proyecto de Eclipse:

Image

El proyecto [springioc-exemples] está configurado para que los archivos fuente y las clases compiladas se encuentren en la raíz de la carpeta del proyecto:

  • [1]: la estructura de carpetas del proyecto [Eclipse]
  • [2]: los archivos de configuración de Spring se encuentran en la raíz del proyecto, es decir, en la carpeta Classpath de la aplicación
  • [3]: las clases del ejemplo 1
  • [4]: las clases del ejemplo 2
  • [5]: las clases del ejemplo 3
  • [6]: las bibliotecas del proyecto [spring-core.jar, spring-beans.jar] se encuentran en la carpeta [dist] de la distribución de Spring, y las de [commons-logging.jar], en la carpeta [lib/jakarta-commons]. Estos tres archivos se han incluido en el Classpath de la aplicación.

15.2.3. Ejemplo 1

Los elementos del ejemplo 1 se han colocado en el paquete [springioc01] del proyecto:

Image

La clase [Personne] es la siguiente:

package istia.st.springioc01;

public class Personne {

    // características
    private String nom;
    private int age;

    // visualización de «Persona»
    public String toString() {
        return "nom=[" + this.nom + "], age=[" + this.age + "]";
    }

    // inicialización-cierre
    public void init() {
        System.out.println("init personne [" + this.toString() + "]");
    }

    public void close() {
        System.out.println("destroy personne [" + this.toString() + "]");
    }

    // getters-setters
    public int getAge() {
        return age;
    }

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

    public String getNom() {
        return nom;
    }

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

}

La clase presenta:

  • líneas 6-7: dos campos privados «nombre» y «edad»
  • líneas 23-38: los métodos de lectura (get) y escritura (set) de estos dos campos
  • líneas 10-12: un método toString para recuperar el valor del objeto [Personne] en forma de cadena de caracteres
  • líneas 15-21: un método «init» que Spring llamará al crear el objeto, y un método «close» que se llamará al destruir el objeto

Para instanciar objetos de tipo [Personne] con Spring, utilizaremos el siguiente archivo [spring-config-01.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="personne1" class="istia.st.springioc01.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Simon" />
        <property name="age" value="40" />
    </bean>
    <bean id="personne2" class="istia.st.springioc01.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Brigitte" />
        <property name="age" value="20" />
    </bean>
</beans>
  • líneas 3 y 12: la etiqueta <beans> es la etiqueta raíz de los archivos de configuración de Spring. Dentro de esta etiqueta, la etiqueta <bean> sirve para definir los distintos objetos que se van a crear.
  • líneas 4-7: definición de un bean
  • línea 4: el bean se llama [personne1] (atributo id) y es una instancia de la clase [istia.st.springioc01.Personne] (atributo class). El método [init] de la instancia se llamará una vez creada esta (atributo init-method) y el método [close] de la instancia se llamará antes de su destrucción (atributo destroy-method).
  • Línea 5: definen el valor que se debe asignar a la propiedad [nom] (atributo name) de la instancia [Personne] creada. Para realizar esta inicialización, Spring utilizará el método [setNom]. Por lo tanto, es necesario que este método exista. Este es el caso aquí.
  • línea 6: lo mismo ocurre con la propiedad [age].
  • Líneas 8-11: definición análoga de un bean denominado [personne2]

La clase de prueba [Main] es la siguiente:

package istia.st.springioc01;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class Main {

    public static void main(String[] args) {
        // gestión del archivo de configuración de Spring
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-01.xml"));
        // Recuperación del bean [personne1]
        Personne personne1 = (Personne) bf.getBean("personne1");
        System.out.println("personne1=" + personne1.toString());
        // Recuperación del bean [personne2]
        Personne personne2 = (Personne) bf.getBean("personne2");
        System.out.println("personne2=" + personne2.toString());
        // Recuperación del bean [personne2] de nuevo
        personne2 = (Personne) bf.getBean("personne2");
        System.out.println("personne2=" + personne2.toString());
        // se eliminan todos los beans
        bf.destroySingletons();
    }
}

Comentarios:

  • línea 10: para obtener los beans definidos en el archivo [spring-config-01.xml], utilizamos un objeto de tipo [XmlBeanFactory] que permite instanciar los beans definidos en un archivo XML. El archivo [spring-config-01.xml] se colocará en el [ClassPath] de la aplicación, c.a.d, en uno de los directorios que explora la máquina virtual Java cuando busca una clase a la que hace referencia la aplicación. El objeto [ClassPathResource] sirve para buscar un recurso en el [ClassPath] de una aplicación, en este caso el archivo [spring-config-01.xml]. El objeto [bf] obtenido (Bean Factory) permite obtener la referencia de un bean denominado «XX» mediante la instrucción bf.getBean("XX").
  • línea 12: se solicita una referencia al bean denominado [personne1] en el archivo [spring-config-01.xml].
  • Línea 13: se muestra el valor del objeto [Personne] correspondiente.
  • Líneas 15-16: se hace lo mismo con el bean denominado [personne2].
  • Líneas 18-19: se vuelve a solicitar el bean denominado [personne2].
  • línea 21: se eliminan todos los beans de [bf] y c.a.d, así como los creados a partir del archivo [spring-config-01.xml].

La ejecución de la clase [Main] ofrece los siguientes resultados:

1
2
3
4
5
6
7
init personne [nom=[Simon], age=[40]]
personne1=nom=[Simon], age=[40]
init personne [nom=[Brigitte], age=[20]]
personne2=nom=[Brigitte], age=[20]
personne2=nom=[Brigitte], age=[20]
destroy personne [nom=[Simon], age=[40]]
destroy personne [nom=[Brigitte], age=[20]]

Comentarios:

  • la línea 1 se obtuvo al ejecutar la línea 12 de [Main]. La operación
Personne personne1 = (Personne) bf.getBean("personne1");

ha forzado la creación del bean [personne1]. Dado que en la definición del bean [personne1] se había escrito [init-method="init"], se ejecutó el método [init] del objeto [Personne] creado. Se muestra el mensaje correspondiente.

  • línea 2: la línea 13 de [Main] ha mostrado el valor del objeto [Personne] creado.
  • Líneas 3-4: se repite el mismo fenómeno con el bean denominado [personne2].
  • línea 5: la operación de las líneas 18-19 de [Main]
    personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());

no ha provocado la creación de un nuevo objeto de tipo [Personne]. Si hubiera sido así, se habría mostrado el método [init]. Este es el principio del singleton. Spring, por defecto, solo crea una única instancia de los beans de su archivo de configuración. Se trata de un servicio de referencias a objetos. Si se le solicita la referencia de un objeto que aún no se ha creado, lo crea y devuelve una referencia al mismo. Si el objeto ya se ha creado, Spring se limita a proporcionar una referencia al mismo. En este caso, dado que [personne2] ya se había creado, Spring se limita a devolver una referencia al mismo.

  • Las salidas de las líneas 6 y 7 se deben a la línea 21 de [Main], que solicita la destrucción de todos los beans a los que hace referencia el objeto [XmlBeanFactory bf], es decir, los beans [personne1, personne2]. Dado que estos dos beans tienen el atributo [destroy-method="close"], se ejecuta el método [close] de ambos beans, lo que provoca la visualización de las líneas 6 y 7.

Ahora que ya conocemos los fundamentos de una configuración de Spring, nuestras explicaciones serán un poco más rápidas.

15.2.4. Ejemplo 2

Los elementos del ejemplo 2 se encuentran en el paquete [springioc02] del proyecto:

Image

El paquete [springioc02] se obtiene primero copiando y pegando el paquete [springioc01]; a continuación, se añade la clase [Voiture] y se adapta la clase [Main] al nuevo ejemplo.

La clase [Voiture] es la siguiente:

package istia.st.springioc02;

public class Voiture {
    // características
    private String marque;
    private String type;
    private Personne propriétaire;

    // constructores
    public Voiture() {
    }

    public Voiture(String marque, String type, Personne propriétaire) {
        setMarque(marque);
        setType(type);
        setPropriétaire(propriétaire);
    }

    // toString
    public String toString() {
        return "Voiture : marque=[" + this.marque + "] type=[" + this.type
                + "] propriétaire=[" + this.propriétaire + "]";
    }

    // getters y setters
    public String getMarque() {
        return marque;
    }

    public void setMarque(String marque) {
        this.marque = marque;
    }

    public Personne getPropriétaire() {
        return propriétaire;
    }

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

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    // init-close
    public void init() {
        System.out.println("init voiture [" + this.toString() + "]");
    }

    public void close() {
        System.out.println("destroy voiture [" + this.toString() + "]");
    }
}

La clase presenta:

  • líneas 5-7: tres campos privados: «tipo», «marca» y «propietario». Estos campos pueden inicializarse y leerse mediante los métodos públicos «get» y «set» de los beans, en las líneas 26-48. También pueden inicializarse mediante el constructor «Coche(String, String, Persona)», definido en las líneas 13-17. La clase también cuenta con un constructor sin argumentos para cumplir con la norma JavaBean.
  • líneas 20-23: un método toString para recuperar el valor del objeto [Voiture] en forma de cadena de caracteres
  • líneas 51-57: un método `init` que Spring llamará justo después de la creación del objeto, y un método `close` que se llamará al destruirse el objeto

Para crear objetos de tipo [Voiture], utilizaremos el siguiente archivo Spring [spring-config-02.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="personne1" class="istia.st.springioc02.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Simon" />
        <property name="age" value="40" />
    </bean>
    <bean id="personne2" class="istia.st.springioc02.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Brigitte" />
        <property name="age" value="20" />
    </bean>
    <bean id="voiture1" class="istia.st.springioc02.Voiture" init-method="init" destroy-method="close">
        <constructor-arg index="0" value="Peugeot" />
        <constructor-arg index="1" value="307" />
        <constructor-arg index="2">
            <ref local="personne2" />
        </constructor-arg>
    </bean>
</beans>

Este archivo añade a los beans definidos en [spring-config-01.xml] un bean con la clave «voiture1» de tipo [Voiture] (líneas 12-17). Para inicializar este bean, podríamos haber escrito:

1
2
3
4
5
6
7
    <bean id="voiture1" class="istia.st.springioc.domain.Voiture" init-method="init" destroy-method="close">
        <property name="marque" value="Peugeot"/>
        <property name="type" value="307"/>
        <property name="propriétaire">
            <ref local="personne2"/>
        </property>
</bean>

En lugar de optar por este método, ya presentado en el ejemplo 1, hemos decidido utilizar aquí el constructor «Voiture(String, String, Personne)» de la clase.

  • línea 12: definición del nombre del bean, de su clase, del método que se ejecutará tras su instanciación y del método que se ejecutará tras su eliminación.
  • línea 13: valor del primer parámetro del constructor [Voiture(String, String, Personne)].
  • línea 14: valor del segundo parámetro del constructor [Voiture(String, String, Personne)].
  • Líneas 15-17: valor del tercer parámetro del constructor [Voiture(String, String, Personne)]. Este parámetro es de tipo [Personne]. Se le proporciona como valor la referencia (etiqueta ref) del bean [personne2] definido en el mismo archivo (atributo local).

Para nuestras pruebas, utilizaremos la siguiente clase [Main]:

package istia.st.springioc02;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class Main {

    public static void main(String[] args) {
        // gestión del archivo de configuración de Spring
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-02.xml"));
        // recuperación del bean [voiture1]
        Voiture Voiture1 = (Voiture) bf.getBean("voiture1");
        System.out.println("Voiture1=" + Voiture1.toString());
        // eliminación de beans
        bf.destroySingletons();
    }
}

El método [main] solicita la referencia del bean [voiture1] (línea 12) y la muestra (línea 13). Los resultados son los siguientes:

1
2
3
4
5
init personne [nom=[Brigitte], age=[20]]
init voiture [Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]]
Voiture1=Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]
destroy voiture [Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]]
destroy personne [nom=[Brigitte], age=[20]]

Comentarios:

  1. El método [main] solicita una referencia al bean [voiture1] (línea 12). Spring inicia la creación del bean [voiture1], ya que este bean aún no se ha creado (singleton). Dado que el bean [voiture1] hace referencia al bean [personne2], este último bean se crea a su vez. Se ha creado el bean [personne2]. A continuación, se ejecuta su método [init] (línea 1) de los resultados. Posteriormente, se instancia el bean [voiture1]. A continuación, se ejecuta su método [init] (línea 2) de los resultados.
  2. La línea 3 de los resultados procede de la línea 13 de [main]: se muestra el valor del bean [voiture1].
  3. La línea 15 de [main] solicita la destrucción de todos los beans existentes, lo que provoca que se muestren las líneas 4 y 5 de los resultados.

15.2.5. Ejemplo 3

Los elementos del ejemplo 3 se encuentran en el paquete [springioc03] del proyecto:

Image

El paquete [springioc03] se obtiene primero copiando y pegando el paquete [springioc01] y, a continuación, se le añade la clase [GroupePersonnes], se elimina la clase [Voiture] y se adapta la clase [Main] al nuevo ejemplo.

La clase [GroupePersonnes] es la siguiente:

package istia.st.springioc03;

import java.util.Map;

public class GroupePersonnes {

    // características
    private Personne[] membres;
    private Map groupesDeTravail;

    // getters y setters
    public Personne[] getMembres() {
        return membres;
    }

    public void setMembres(Personne[] membres) {
        this.membres = membres;
    }

    public Map getGroupesDeTravail() {
        return groupesDeTravail;
    }

    public void setGroupesDeTravail(Map groupesDeTravail) {
        this.groupesDeTravail = groupesDeTravail;
    }

    // visualización
    public String toString() {
        String liste = "membres : ";
        for (int i = 0; i < this.membres.length; i++) {
            liste += "[" + this.membres[i].toString() + "]";
        }
        return liste + ", groupes de travail = "
                + this.groupesDeTravail.toString();
    }

    // inicialización y cierre
    public void init() {
        System.out.println("init GroupePersonnes [" + this.toString() + "]");
    }

    public void close() {
        System.out.println("destroy GroupePersonnes [" + this.toString() + "]");
    }
}

Sus dos miembros privados son:

línea 8: miembros: una matriz con las personas que forman parte del grupo

línea 9: groupesDeTravail: un diccionario que asigna a una persona a un grupo de trabajo

Cabe señalar aquí que la clase [GroupePersonnes] no define ningún constructor sin argumentos. Recordemos que, en ausencia de cualquier constructor, existe un constructor «por defecto», que es el constructor sin argumentos y que no hace nada.

Lo que se pretende mostrar aquí es cómo Spring permite inicializar objetos complejos, como aquellos que tienen campos de tipo matriz o diccionario. El archivo de beans [spring-config-03.xml] del ejemplo 3 es el siguiente:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="personne1" class="istia.st.springioc03.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Simon" />
        <property name="age" value="40" />
    </bean>
    <bean id="personne2" class="istia.st.springioc03.Personne" init-method="init" destroy-method="close">
        <property name="nom" value="Brigitte" />
        <property name="age" value="20" />
    </bean>
    <bean id="groupe1" class="istia.st.springioc03.GroupePersonnes" init-method="init" destroy-method="close">
        <property name="membres">
            <list>
                <ref local="personne1" />
                <ref local="personne2" />
            </list>
        </property>
        <property name="groupesDeTravail">
            <map>
                <entry key="Brigitte" value="Marketing" />
                <entry key="Simon" value="Ressources humaines" />
            </map>
        </property>
    </bean>
</beans>
  • líneas 14-17: la etiqueta <list> permite inicializar un campo de tipo matriz o que implemente la interfaz List con diferentes valores.
  • líneas 20-23: la etiqueta <map> permite hacer lo mismo con un campo que implemente la interfaz Map.

Para nuestras pruebas, utilizaremos la siguiente clase [Main]:

package istia.st.springioc03;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class Main {

    public static void main(String[] args) {
        // gestión del archivo de configuración de Spring
        final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-03.xml"));
        // Recuperación del bean [groupe1]
        GroupePersonnes groupe1 = (GroupePersonnes) bf.getBean("groupe1");
        System.out.println("groupe1=" + groupe1.toString());
        // se eliminan los beans
        bf.destroySingletons();
    }
}
  • líneas 12-13: se solicita a Spring una referencia al bean [groupe1] y se muestra su valor.

Los resultados obtenidos son los siguientes:

1
2
3
4
5
6
7
init personne [nom=[Simon], age=[40]]
init personne [nom=[Brigitte], age=[20]]
init GroupePersonnes [membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}]
groupe1=membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}
destroy GroupePersonnes [membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}]
destroy personne [nom=[Simon], age=[40]]
destroy personne [nom=[Brigitte], age=[20]]

Comentarios:

  • En la línea 12 de [Main], se solicita una referencia al bean [groupe1]. Spring inicia la creación de este bean. Dado que el bean [groupe1] hace referencia a los beans [personne1] y [personne2], se crean estos dos beans (líneas 1 y 2 de los resultados). A continuación, se instancia el bean [groupe1] y se ejecuta su método [init] (línea 3 de los resultados).
  • La línea 13 de [Main] muestra la línea 4 de los resultados.
  • La línea 15 de [Main] muestra las líneas 5-7 de los resultados.

15.3. Configuración de una aplicación de n capas con Spring

Consideremos una aplicación de tres capas con la siguiente estructura:

utilisateurDonnéesCouche: función [metier]: capa de acceso a datos [dao]: capa de interfaz de usuario [ui]

Aquí nos proponemos mostrar las ventajas de Spring a la hora de construir una arquitectura de este tipo.

  • Las tres capas serán independientes gracias al uso de interfaces Java
  • La integración de las tres capas la llevará a cabo Spring

La estructura de la aplicación en Eclipse podría ser la siguiente:

  • [1]: la capa [dao]:
    • [IDao]: la interfaz de la capa
    • [Dao1, Dao2]: dos implementaciones de esta interfaz
  • [2]: la capa [metier]:
    • [IMetier]: la interfaz de la capa
    • [Metier1, Metier2]: dos implementaciones de esta interfaz
  • [3]: la capa [ui]:
    • [IUi]: la interfaz de la capa
    • [Ui1, Ui2]: dos implementaciones de esta interfaz
  • [4]: los archivos de configuración de Spring de la aplicación. Configuraremos la aplicación de dos maneras.
  • [5]: las bibliotecas necesarias para la aplicación. Son las mismas que se han utilizado en los ejemplos anteriores.
  • [6]: el paquete de pruebas. [Main1] utilizará la configuración de [spring-config-01.xml] y [Main2] la configuración de [spring-config-02.xml].

El objetivo de este ejemplo es mostrar que podemos cambiar la implementación de una o varias capas de la aplicación sin que ello afecte en absoluto a las demás capas. Todo se gestiona en el archivo de configuración de Spring.


La capa [dao]


La capa [dao] implementa la siguiente interfaz [IDao]:

1
2
3
4
5
package istia.st.springioc.troistier.dao;

public interface IDao {
    public int doSomethingInDaoLayer(int a, int b);
}

La implementación [Dao1] será la siguiente:

1
2
3
4
5
6
7
8
package istia.st.springioc.troistier.dao;

public class Dao1 implements IDao {

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

La implementación [Dao2] será la siguiente:

1
2
3
4
5
6
7
8
package istia.st.springioc.troistier.dao;

public class Dao2 implements IDao {

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

La capa [métier]


La capa [métier] implementa la siguiente interfaz [IMetier]:

1
2
3
4
5
package istia.st.springioc.troistier.metier;

public interface IMetier {
      public int doSomethingInBusinessLayer(int a, int b);
}

La implementación [Metier1] será la siguiente:

package istia.st.springioc.troistier.metier;

import istia.st.springioc.troistier.dao.IDao;

public class Metier1 implements IMetier {

    // capa [dao]
    private IDao dao = null;

    public IDao getDao() {
        return dao;
    }

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

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

}

La implementación [Metier2] será la siguiente:

package istia.st.springioc.troistier.metier;

import istia.st.springioc.troistier.dao.IDao;

public class Metier2 implements IMetier {

    // capa [dao]
    private IDao dao = null;

    public IDao getDao() {
        return dao;
    }

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

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

}

La capa [ui]


La capa [ui] implementa la siguiente interfaz [IUi]:

1
2
3
4
5
package istia.st.springioc.troistier.ui;

public interface IUi {
    public int doSomethingInUiLayer(int a, int b);
}

La implementación [Ui1] será la siguiente:

package istia.st.springioc.troistier.ui;

import istia.st.springioc.troistier.metier.IMetier;

public class Ui1 implements IUi {

    // capa [business]
    private IMetier metier = null;

    public IMetier getMetier() {
        return metier;
    }

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

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

}

La implementación [Ui2] será la siguiente:

package istia.st.springioc.troistier.ui;

import istia.st.springioc.troistier.metier.IMetier;

public class Ui2 implements IUi {

    // capa [business]
    private IMetier metier = null;

    public IMetier getMetier() {
        return metier;
    }

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

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

}

Los archivos de configuración de Spring


El primero, [spring-config-01.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- la clase DAO -->
    <bean id="dao" class="istia.st.springioc.troistier.dao.Dao1"/>
    <!-- la clase de negocio -->
    <bean id="metier"     class="istia.st.springioc.troistier.metier.Metier1">
        <property name="dao">
            <ref local="dao" />
        </property>
    </bean>
    <!-- la clase UI -->
    <bean id="ui" class="istia.st.springioc.troistier.ui.Ui1">
        <property name="metier">
            <ref local="metier" />
        </property>
    </bean>
</beans>

El segundo, [spring-config-02.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- la clase DAO -->
    <bean id="dao" class="istia.st.springioc.troistier.dao.Dao2"/>
    <!-- la clase de negocio -->
    <bean id="metier"
        class="istia.st.springioc.troistier.metier.Metier2">
        <property name="dao">
            <ref local="dao" />
        </property>
    </bean>
    <!-- la clase UI -->
    <bean id="ui" class="istia.st.springioc.troistier.ui.Ui2">
        <property name="metier">
            <ref local="metier" />
        </property>
    </bean>
</beans>

Los programas de prueba


El programa [Main1] es el siguiente:

package istia.st.springioc.troistier.main;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

import istia.st.springioc.troistier.ui.IUi;

public class Main1 {

    public static void main(String[] args) {
        // se obtiene una implementación de la interfaz IUi
        IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-01.xml"))).getBean("ui");
        // se utiliza la clase
        int a = 10, b = 20;
        int res = ui.doSomethingInUiLayer(a, b);
        // se muestra el resultado
        System.out.println("ui(" + a + "," + b + ")=" + res);
    }

}

El programa [Main1] utiliza el archivo de configuración [spring-config-01.xml] y, por lo tanto, las implementaciones [Ui1, Metier1, Dao1] de las capas. Los resultados obtenidos en la consola de Eclipse:

ui(10,20)=34

El programa [Main2] es el siguiente:

package istia.st.springioc.troistier.main;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

import istia.st.springioc.troistier.ui.IUi;

public class Main2 {

    public static void main(String[] args) {
        // se obtiene una implementación de la interfaz IUi
        IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-02.xml"))).getBean("ui");
        // se utiliza la clase
        int a = 10, b = 20;
        int res = ui.doSomethingInUiLayer(a, b);
        // se muestra el resultado
        System.out.println("ui(" + a + "," + b + ")=" + res);
    }

}

El programa [Main2] utiliza el archivo de configuración [spring-config-02.xml] y, por lo tanto, las implementaciones [Ui2, Metier2, Dao2] de las capas. Resultados obtenidos en la consola de Eclipse:

ui(10,20)=-10

15.4. Conclusión

La aplicación que hemos creado ofrece una gran flexibilidad de evolución. La implementación de una capa se puede modificar mediante una simple configuración. El código de las demás capas permanece inalterado. Esto se consigue gracias al concepto IoC, que es uno de los dos pilares de Spring. El otro pilar es AOP (programación orientada a aspectos), que no hemos presentado. Permite añadir, también mediante configuración, «comportamiento» a un método de clase sin modificar el código de esta.