Skip to content

2. Artículo 1 - Spring IoC

Objetivos del documento:

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

2.1. Configurar una aplicación de tres capas con Spring

Consideremos una aplicación clásica de tres capas:

Supondremos que el acceso a las capas de negocio y DAO está controlado por interfaces de Java:

  1. la interfaz [IArticlesDao] para la capa de acceso a datos
  1. la interfaz [IArticlesManager] para la capa de negocio

En la capa de acceso a datos o capa DAO (Data Access Object), es común trabajar con un SGBD y, por lo tanto, con un controlador JDBC. Consideremos el esqueleto de una clase que accede a una tabla de artículos en un SGBD:

public class ArticlesDaoPlainJdbc implements IArticlesDao {

     // conexión a la fuente de datos
    private String driverClassName=null;
    private Connection connexion=null;
    private String url = null;
    private String user = null;
    private String pwd = null;
 ....

    public List getAllArticles() {
        // se solicita la lista de artículos
        try {
             // se carga el controlador JDBC
            Class.forName(driverClassName);
            // se crea una conexión a la BD
            connexion = DriverManager.getConnection(url, user, pwd);
            ...
        } catch (SQLException ex) {
            ...
        } finally {
            ...
        }
    }

Para realizar una operación en el SGBD, cualquier método necesita un objeto [Connection] que represente la conexión a la base de datos, a través de la cual transitarán los intercambios entre esta y el código Java. Para construir este objeto, se necesitan cuatro datos:

String driverClassName
el nombre de la clase del controlador JDBC del SGBD
String url
la URL de la base de datos que se va a utilizar
String user
la identidad con la que se establece la conexión
String pwd
la contraseña de esta identidad

¿Cómo puede nuestra clase [ArticlesDaoPlainJdbc] anterior obtener esta información? Existen varias posibilidades:

solución 1: la información está codificada de forma estática en la clase:

1
2
3
4
5
6
7
8
public class ArticlesDaoPlainJdbc implements IArticlesDao {

     // conexión a la fuente de datos
    private final String driverClassName = "org.firebirdsql.jdbc.FBDriver";
    private String url = "jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb";
    private String user = "someone";
    private String pwd = "somepassword";
 ....

La desventaja de esta solución es que hay que modificar el código Java cada vez que se produce un cambio en esta información, por ejemplo, al cambiar la contraseña.

Solución 2: la información se transmite al objeto al momento de su construcción:

public class ArticlesDaoPlainJdbc implements IArticlesDao {

     // conexión a la fuente de datos
    private final String driverClassName;
    private String url;
    private String user;
    private String pwd;
 ....
    public ArticlesDaoPlainJdbc(String driverClassName,String url,String user,String pwd) {
      this.driverClassName=driverClassName;
    this.url=url;
    this.user=user;
    this.pwd=pwd;
    ...
    }

Aquí, el objeto recibe, al crearse, la información que necesita para funcionar. El problema se traslada entonces al código que le transmitió los cuatro datos. ¿Cómo los obtuvo? La siguiente clase [ArticlesManagerWithDataBase] de la capa de negocio podría crear un objeto [ArticlesDaoPlainJdbc] de la capa de acceso a datos:

public class ArticlesManagerWithDataBase implements IArticlesManager {

    // una instancia de acceso a los datos
    private IArticlesDao articlesDao;
 ....
    public ArticlesManagerWithDataBase (String driverClassName, String url, String user, String pwd, ...) {
        ... 
         // creación del servicio de acceso a datos
        articlesDao =(IArticlesDao)new ArticlesDaoPlainJdbc(driverClassName,url,user,pwd);
    ...
    }

    public ... doSomething(...){
        ...
    }
}

Se observa que, una vez más, la información necesaria para la construcción del objeto [ArticlesDaoPlainJdbc] se le proporciona al constructor del objeto [ArticlesManagerWithDataBase]. Podemos imaginar que esta información le es transmitida por una capa superior, como la capa de interfaz con el usuario. De esta manera, paso a paso, llegamos a la capa más alta de la aplicación. Debido a su posición, esta no es llamada por ninguna capa que pueda transmitirle la información de configuración que necesita. Por lo tanto, es necesario encontrar una solución alternativa a la configuración mediante el constructor. La solución habitual para configurar una aplicación en su capa más alta es utilizar un archivo que contenga toda la información susceptible de cambiar con el tiempo. Puede haber varios archivos de este tipo. Al iniciar la aplicación, una capa de inicialización creará entonces todos o parte de los objetos necesarios para las diferentes capas de la aplicación.

Existe una gran variedad de archivos de configuración. La tendencia actual es utilizar archivos XML. Esta es la opción elegida por Spring. El archivo que configura un objeto [ArticlesDaoPlainJdbc] podría ser el siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- la clase de acceso a datos -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
        <constructor-arg index="0">
            <value>org.firebirdsql.jdbc.FBDriver</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>someone</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>somepassword</value>
        </constructor-arg>
    </bean>
</beans>

Una aplicación es un conjunto de objetos que Spring denomina «beans», ya que siguen la norma JavaBean para nombrar los accesores e inicializadores (getters/setters) de los campos privados de un objeto. Los objetos que, en una aplicación, tienen la función de prestar un servicio, suelen crearse en una sola instancia. Se les llama singletons. Así, en nuestro ejemplo de aplicación de múltiples capas que estamos analizando aquí, el acceso a la base de datos de artículos estará a cargo de un único ejemplar de la clase [ArticlesDaoPlainJdbc]. En una aplicación web, estos objetos de servicio atienden a varios clientes al mismo tiempo. No se crea un objeto de servicio por cada cliente.

El archivo de configuración de Spring anterior permite crear un único objeto de servicio de tipo [ArticlesDaoPlainJdbc] en un paquete llamado [istia.st.articles.dao]. Los cuatro datos necesarios para el constructor de este objeto se definen dentro de una etiqueta <bean>...</bean>. Habrá tantas etiquetas <bean> como singletons se deban construir.

¿En qué momento se llevará a cabo la construcción de los objetos definidos en el archivo de Spring? La inicialización de una aplicación puede delegarse al método main de esa misma aplicación, si es que cuenta con uno. En el caso de una aplicación web, podría ser el método [init] del servlet principal. En toda aplicación hay un método que, con certeza, será el primero en ejecutarse. Por lo general, es en este donde se lleva a cabo la construcción de los singletons.

Veamos un ejemplo. Supongamos que queremos probar la clase [ArticlesDaoPlainJdbc] anterior mediante una prueba JUnit. Una clase de prueba JUnit cuenta con un método [setUp] que se ejecuta antes que cualquier otro método. Es ahí donde se creará el singleton [ArticlesDaoPlainJdbc].

Si seguimos la solución de pasar la información de configuración mediante el constructor, tendremos la siguiente clase de prueba:

public class TestArticlesPlainJdbc extends TestCase {
    // prueba la clase de acceso a los artículos ArticlesDaoPlainJdbc
     // la fuente de datos se define en sprintest

     // una instancia de la clase probada
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception{
        // obtiene una instancia de acceso a datos
        articlesDao =
            (IArticlesDao) new ArticlesDaoPlainJdbc("org.firebirdsql.jdbc.FBDriver",
                "jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb","someone","somepassword");
    }

La clase de llamada [TestArticlesPlainJdbc] debe conocer los cuatro datos necesarios para la inicialización del singleton [ArticlesDaoPlainJdbc] que se va a construir.

Si seguimos la solución de pasar la información de configuración mediante un archivo de configuración, podríamos tener la siguiente clase de prueba utilizando el archivo de Spring descrito anteriormente.

public class TestSpringArticlesPlainJdbc extends TestCase {
    // prueba la clase de acceso a los artículos ArticlesDaoJdbc
     // la fuente de datos está definida en Sprintest

     // una instancia de la clase probada
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception {
      // obtiene una instancia de acceso a datos
      articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
          "springArticlesPlainJdbc.xml"))).getBean("articlesDao");
    }

En este caso, la clase de llamada [TestSpringArticlesPlainJdbc] no necesita conocer la información necesaria para la inicialización del singleton que se va a construir. Solo necesita saber:

  1. [springArticlesPlainJdbc.xml]: el nombre del archivo de configuración de Spring descrito anteriormente
  2. [articlesDao]: el nombre del singleton que se va a crear

Cualquier modificación en el archivo de configuración, fuera de estas dos entidades, no tiene ningún impacto en el código Java. Este método de configuración de los objetos de una aplicación es muy flexible. Para configurarse, la aplicación solo necesita saber dos cosas:

  • el nombre del archivo de Spring que contiene la definición de los singletons que se deben crear
  • los nombres de estos singletons, que el código Java utiliza para obtener una referencia a los objetos a los que se han asociado gracias al archivo de configuración

2.2. Inyección de dependencias e inversión de control

Introduzcamos ahora el concepto de inyección de dependencias (Dependency Injection) que utiliza Spring para configurar las aplicaciones. También se utiliza el término inversión de control (IoC, Inversion of Control). Consideremos la creación del singleton [ArticlesManagerWithDataBase] de la capa de negocio de nuestra aplicación:

Para acceder a los datos del SGBD, la capa de negocio debe utilizar los servicios de un objeto que implemente la interfaz [IArticlesDao], por ejemplo, un objeto de tipo [ArticlesDaoPlainJdbc]. El código de la clase [ArticlesManagerWithDataBase] podría verse así:

public class ArticlesManagerWithDataBase implements IArticlesManager {

     // una instancia de acceso a datos
    private IArticlesDao articlesDao;
 ....
    public ArticlesManagerWithDataBase (String driverClassName, String url, String user, String pwd, ...) {
        ... 
         // creación del servicio de acceso a datos
        articlesDao =(IArticlesDao)new ArticlesDaoPlainJdbc(driverClassName,url,user,pwd);
    ...
    }

    public ... doSomething(...){
        ...
    }
}

Se supone que la clase [ArticlesDaoPlainJdbc] implementa aquí una interfaz [IArticlesDao]:

public class ArticlesDaoPlainJdbc implements IArticlesDao {...}

Para crear el singleton de tipo [IArticlesDao] necesario para el funcionamiento de la clase, el constructor de esta utiliza explícitamente el nombre de la clase de implementación de la interfaz [IArticlesDao]:

articlesDao =(IArticlesDao) new ArticlesDaoPlainJdbc(...);

Por lo tanto, existe una dependencia rígida en el código respecto al nombre de la clase. Si la clase de implementación de la interfaz [IArticlesDao] llegara a cambiar, habría que modificar el código del constructor anterior. Existen las siguientes relaciones entre los objetos:

La clase [ArticlesManagerWithDataBase] toma por sí misma la iniciativa de crear el objeto [ArticlesDaoPlainJdbc] que necesita. Volviendo al término «inversión de control», se diría que es ella quien tiene el «control» para crear el objeto que necesita.

Si tuviéramos que escribir una clase de prueba JUnit para la clase [ArticlesManagerWithDataBase], podríamos tener algo como lo siguiente:

public class TestArticlesManagerWithDataBase extends TestCase {
    // una instancia de la clase de negocio que se está probando
    private IArticlesManager articlesManager;

    protected void setUp() throws Exception {
        // crea una instancia de la clase de negocio que se está probando
        articlesManager =
            (IArticlesManager) new ArticlesManagerWithDataBase("org.firebirdsql.jdbc.FBDriver",
                "jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb","someone","somepassword");
    }

La clase de prueba crea una instancia de la clase de negocio [ArticlesManagerWithDataBase], la cual, a su vez, crea en su constructor una instancia de la clase de acceso a datos [ArticlesDaoPlainJdbc].

La solución con Spring eliminará la necesidad de que la clase de negocio [ArticlesManagerWithDataBase] conozca el nombre [ArticlesDaoPlainJdbc] de la clase de acceso a datos que necesita. Esto permitirá cambiarlo sin modificar el código Java de la clase de negocio. Spring permitirá crear al mismo tiempo los dos singletons: el de la capa de acceso a datos y el de la capa de negocio. El archivo de configuración de Spring definirá un nuevo bean:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- la clase de acceso a datos -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
        <constructor-arg index="0">
            <value>org.firebirdsql.jdbc.FBDriver</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>someone</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>somepassword</value>
        </constructor-arg>
    </bean>
    <bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
        <property name="articlesDao">
            <ref bean="articlesDao"/>
        </property>
    </bean>
</beans>

La novedad radica en el bean que define el singleton de la clase de negocio que se va a crear:

    <bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
        <property name="articlesDao">
            <ref bean="articlesDao"/>
        </property>
    </bean>
  1. Se define la clase que implementa el bean [articlesManager]: [ArticlesManagerWithDataBase]
  2. el campo [articlesDao] del bean recibe un valor mediante la etiqueta <property name="articlesDao">. Se trata del campo definido en la clase [ArticlesManagerWithDataBase]:
public class ArticlesManagerWithDataBase implements IArticlesManager {

  // interfaz de acceso a datos
  private IArticlesDao articlesDao;

  public IArticlesDao getArticlesDao() {
    return articlesDao;
  }

  public void setArticlesDao(IArticlesDao articlesDao) {
    this.articlesDao = articlesDao;
  }

Para que Spring pueda inicializar el campo [articlesDao] mediante su etiqueta <property>, el campo debe cumplir con la norma JavaBean y debe existir un método [setArticlesDao] para inicializar el campo [articlesDao]. Cabe destacar que el nombre del método se deriva de manera muy precisa del nombre del campo. Paralelamente, suele existir un método [get...] para obtener el valor del campo. En este caso, es el método [getArticlesDao]. En esta nueva versión, la clase [ArticlesManagerWithDataBase] ya no tiene constructor. Ya no lo necesita.

  • El valor que Spring asignará al campo [articlesDao] es el del bean [articlesDao] definido en su archivo de configuración:
    <bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
        <property name="articlesDao">
            <ref bean="articlesDao"/>
        </property>
    </bean>
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
        <constructor-arg index="0">
    .............
    </bean>
  • Cuando Spring construya el singleton [ArticlesManagerWithDataBase], también tendrá que crear el singleton [ArticlesDaoPlainJdbc]:
    • Spring establecerá un grafo de dependencias de los beans y verá que el bean [articlesManager] depende del bean [articlesDao]
    • creará el bean [articlesDao], es decir, un objeto de tipo [ArticlesDaoPlainJdbc]
    • luego creará el bean [articlesManager] de tipo [ArticlesManagerWithDataBase]

Imaginemos ahora una prueba JUnit para la clase [ArticlesManagerWithDataBase]. Podría verse así:

public class TestSpringArticlesManagerWithDataBase extends TestCase {
    // prueba la clase de negocio [ArticlesManagerWithDataBase]

    // una instancia de la clase de negocio probada
    private IArticlesManager articlesManager;

    protected void setUp() throws Exception {
      // obtiene una instancia de acceso a datos
      articlesManager = (IArticlesManager) (new XmlBeanFactory(new ClassPathResource(
          "springArticlesManagerWithDataBase.xml"))).getBean("articlesManager");
    }

Veamos el proceso de creación de los dos singletons definidos en el archivo de Spring llamado [springArticlesManagerWithDataBase.xml].

  • El método [setUp] anterior solicita una referencia del bean llamado [articlesManager]
  • Spring consulta su archivo de configuración y encuentra el bean [articlesManager]. Si ya está creado, simplemente devuelve una referencia al objeto (singleton); de lo contrario, lo crea.
  • Spring detecta la dependencia del bean [articlesManager] respecto al bean [articlesDao]. Por lo tanto, crea el singleton [articlesDao] de tipo [ArticlesDaoPlainJdbc] si este aún no existe (singleton).
  • Crea el singleton [articlesManager] de tipo [ArticlesManagerWithDataBase]

Este mecanismo podría esquematizarse de la siguiente manera:

Recordemos la estructura básica de la clase [ArticlesManagerWithDataBase]:

public class ArticlesManagerWithDataBase implements IArticlesManager {

  // interfaz de acceso a datos
  private IArticlesDao articlesDao;

  public IArticlesDao getArticlesDao() {
    return articlesDao;
  }

  public void setArticlesDao(IArticlesDao articlesDao) {
    this.articlesDao = articlesDao;
  }

Al finalizar la construcción de los singletons por parte de Spring, tenemos un objeto de tipo [ArticlesManagerWithDataBase] cuyo campo [articlesDao] se ha inicializado sin que él sepa cómo. Se dice que se ha inyectado una dependencia en el objeto [ArticlesManagerWithDataBase]. También se dice que se ha invertido el control: ya no es el objeto [ArticlesManagerWithDataBase] el que toma la iniciativa de crear por sí mismo el objeto que implementa la interfaz [IArticlesDao] que necesita, sino que es la aplicación en el nivel más alto (al inicializarse) la que se encarga de crear todos los objetos que necesita, gestionando las dependencias entre ellos.

La principal ventaja de configurar el singleton [ArticlesManagerWithDataBase] mediante un archivo de Spring es que ahora se puede cambiar la clase de implementación correspondiente al campo [articlesDao] de la clase [ArticlesManagerWithDataBase] sin necesidad de modificar el código de esta última. Solo hay que cambiar el nombre de la clase en la definición del bean [articlesDao] en el archivo de Spring:

    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
...
    </bean>

se convertirá, por ejemplo, en:

    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoIbatisSqlMap">
...
    </bean>

El bean [ArticlesManagerWithDataBase] funcionará con esta nueva clase de acceso a datos, sin siquiera darse cuenta.

2.3. Spring IoC en la práctica

2.3.1. Ejemplo 1

Consideremos la siguiente clase:

package istia.st.springioc.domain;

public class Personne {
  private String nom;
  private int age;

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

   // inicialización y 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:

  • dos campos privados: nombre y edad
  • los métodos de lectura (get) y escritura (set) de estos dos campos
  • un método toString para recuperar el valor del objeto [Personne] en forma de cadena de caracteres
  • un método init que Spring llamará al crear el objeto, y un método close que se llamará al destruir el objeto

Para crear objetos de tipo [Personne], utilizaremos el siguiente archivo de Spring:

<?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.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </bean>
    <bean id="personne2" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </bean>
</beans>

Este archivo se llamará config.xml.

  • Define dos beans con las claves respectivas «persona1» y «persona2» de tipo [Personne]
  • Inicializa los campos [nom, age] de cada persona
  • define los métodos que se deben llamar durante la construcción inicial del objeto [init-method] y durante la destrucción del objeto [destroy-method]

Para nuestras pruebas, utilizaremos una única clase de prueba JUnit a la que iremos agregando métodos sucesivamente. La primera versión de esta clase será la siguiente:

package istia.st.springioc.tests;

import istia.st.springioc.domain.Personne;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import junit.framework.TestCase;

public class Tests extends TestCase {

   // fábrica de beans
  private ListableBeanFactory bf;

   // pruebas de inicialización
  public void setUp() {
    bf = new XmlBeanFactory(new ClassPathResource("config.xml"));
  }

  public void test1() {
    // recuperación de los beans [Personne] del archivo Spring mediante su clave
    Personne personne1 = (Personne) bf.getBean("personne1");
    System.out.println("personne1=" + personne1.toString());
    Personne personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());
    personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());
  }
}

Comentarios:

  • Para obtener los beans definidos en el archivo [config.xml], utilizamos un objeto de tipo [ListableBeanFactory]. Existen otros tipos de objetos que permiten acceder a los beans. El objeto [ListableBeanFactory] se obtiene en el método [setUp] de la clase de prueba y se almacena en una variable privada. De esta manera, estará disponible para todos los métodos de prueba.
  • El archivo [config.xml] se colocará en el [ClassPath] de la aplicación, c.a.d, en uno de los directorios que explora la máquina virtual de 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 [config.xml].
  • Spring puede utilizar archivos de configuración en diversos formatos. El objeto [XmlBeanFactory] permite analizar un archivo de configuración en formato XML.
  • Al procesar un archivo de Spring se obtiene un objeto de tipo [ListableBeanFactory], en este caso el objeto bf. Con este objeto, se puede obtener un bean identificado por la clave C mediante bf.getBean(C).
  • El método [test1] solicita y muestra el valor de los beans con las claves «persona1» y «persona2».

La estructura del proyecto Eclipse de nuestra aplicación es la siguiente:

Image

Comentarios:

  • La carpeta [src] contiene los códigos fuente. Los códigos compilados se guardarán en una carpeta [bin] que no se muestra aquí.
  • El archivo [config.xml] se encuentra en la raíz de la carpeta [src]. Al compilar el proyecto, este se copia automáticamente a la carpeta [bin], que forma parte de la carpeta [ClassPath] de la aplicación. Es ahí donde lo busca el objeto [ClassPathResource].
  • La carpeta [lib] contiene tres bibliotecas Java necesarias para la aplicación:
      • commons-logging.jar y spring-core.jar para las clases de Spring
      • junit.jar para las clases de JUnit
  • la carpeta [lib] también forma parte del [ClassPath] de la aplicación

La ejecución del método [test1] de la prueba JUnit arroja los siguientes resultados:

18 sept. 2004 11:28:53 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
18 sept. 2004 11:28:53 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne1'
init personne [nom=[Simon], age=[40]]
personne1=nom=[Simon], age=[40]
18 sept. 2004 11:28:53 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne2'
init personne [nom=[Brigitte], age=[20]]
personne2=nom=[Brigitte], age=[20]
personne2=nom=[Brigitte], age=[20]

Comentarios:

  • Spring registra una serie de eventos mediante la biblioteca [commons-logging.jar]. Estos registros nos permiten comprender mejor el funcionamiento de Spring.
  • El archivo [config.xml] se cargó y luego se procesó
  • la operación*
Personne personne1 = (Personne) bf.getBean("personne1");

forzó la creación del bean [personne1]. Se puede ver el registro de Spring al respecto. Debido a que en la definición del bean [personne1] habíamos escrito [init-method="init"], se ejecutó el método [init] del objeto [Personne] creado. Se muestra el mensaje correspondiente.

  • La operación
System.out.println("personne1=" + personne1.toString());

ha mostrado el valor del objeto [Personne] creado.

  • El mismo fenómeno se repite para el bean de clave [personne2].
  • La última operación
    personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());

no provocó la creación de un nuevo objeto de tipo [Personne]. Si hubiera sido así, se habría mostrado el método [init], lo cual no ocurre aquí. Este es el principio del singleton. Spring, por defecto, crea solo una instancia de los beans de su archivo de configuración. Es un servicio de referencias de objetos. Si se le solicita la referencia de un objeto que aún no se ha creado, lo crea y devuelve una referencia. Si el objeto ya se ha creado, Spring simplemente devuelve una referencia.

  • Se puede observar que no hay rastro alguno del método [close] del objeto [Personne], a pesar de que lo habíamos escrito en la definición de los beans [destroy-method=close]. Es posible que este método solo se ejecute cuando el recolector de basura (garbage collector) recupere la memoria ocupada por el objeto. En el momento en que esto ocurre, la aplicación ya ha finalizado y la salida a pantalla no tiene ningún efecto. Por verificar.

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

2.3.2. Ejemplo 2

Consideremos la siguiente clase nueva [Voiture]:

package istia.st.springioc.domain;

public class Voiture {
  private String marque;
  private String type;
  private Personne propriétaire;

   // constructores

  public Voiture() {
  }

  public Voiture(String marque, String type, Personne propriétaire) {
    this.marque = marque;
    this.type = type;
    this.proprié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;
  }

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

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

}

La clase presenta:

  • 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. También pueden inicializarse mediante el constructor Auto(String, String, Persona). La clase cuenta además con un constructor sin argumentos para cumplir con la norma JavaBean.
  • un método toString para recuperar el valor del objeto [Voiture] en forma de cadena de caracteres
  • un método init que Spring llamará inmediatamente después de la creación del objeto, y un método close que se llamará al destruir el objeto

Para crear objetos de tipo [Voiture], utilizaremos el siguiente archivo de Spring [config.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.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </bean>
    <bean id="personne2" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </bean>
    <bean id="voiture1" class="istia.st.springioc.domain.Voiture" 
        init-method="init" destroy-method="close">
        <constructor-arg index="0">
            <value>Peugeot</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>307</value>
        </constructor-arg>
        <constructor-arg index="2">
            <ref bean="personne2"></ref>
        </constructor-arg>
    </bean>
</beans>

Este archivo agrega a las definiciones anteriores un bean con la clave «voiture1» de tipo [Voiture]. Para inicializar este bean, se podría haber escrito:

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

En lugar de optar por este método ya presentado, aquí hemos decidido utilizar el constructor Carro(String, String, Persona) de la clase. Por otra parte, el bean [voiture1] define el método que se debe llamar durante la construcción inicial del objeto [init-method] y el que se debe llamar durante la destrucción del objeto [destroy-method].

Para nuestras pruebas, utilizaremos la clase de prueba JUnit ya presentada, añadiéndole el siguiente método [test2]:

1
2
3
4
5
  public void test2() {
     // recuperación del bean [voiture1]
    Voiture Voiture1 = (Voiture) bf.getBean("voiture1");
    System.out.println("Voiture1=" + Voiture1.toString());
  }

El método [test2] recupera el bean [voiture1] y lo muestra.

La estructura del proyecto de Eclipse sigue siendo la misma que en la prueba anterior. Al ejecutar el método [test2] de la prueba JUnit se obtienen los siguientes resultados:

18 sept. 2004 14:56:10 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
18 sept. 2004 14:56:10 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'voiture1'
18 sept. 2004 14:56:10 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne2'
init personne [nom=[Brigitte], age=[20]]
18 sept. 2004 14:56:10 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'voiture1' instantiated via constructor [public istia.st.springioc.domain.Voiture(java.lang.String,java.lang.String,istia.st.springioc.domain.Personne)]
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]]

Comentarios:

  1. el método [test2] solicita una referencia al bean [voiture1]
  2. línea 4: Spring inicia la creación del bean [voiture1], ya que este bean aún no se ha creado (singleton)
  3. línea 6: dado que el bean [voiture1] hace referencia al bean [personne2], este último bean se crea a su vez
  4. línea 7: se ha creado el bean [personne2]. A continuación, se ejecuta su método [init].
  5. línea 9: Spring indica que utilizará un constructor para crear el bean [voiture1]
  6. línea 10: se ha creado el bean [voiture1]. A continuación, se ejecuta su método [init].
  7. línea 11: el método [test2] muestra el valor del bean [voiture1]

2.3.3. Ejemplo 3

Introducimos la siguiente clase nueva [GroupePersonnes]:

package istia.st.springioc.domain;

import java.util.Map;

public class GroupePersonnes {
  private Personne[] membres;
  private Map groupesDeTravail;

   // getters - 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-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:

miembros: una matriz con las personas que forman parte del grupo

groupesDeTravail: un diccionario que asigna a una persona a un grupo de trabajo

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

El objetivo aquí es mostrar cómo Spring permite inicializar objetos complejos, tales como aquellos que contienen campos de tipo matriz o diccionario. Agregamos un nuevo bean al archivo Spring [config.xml] anterior:

    <bean id="groupe1" class="istia.st.springioc.domain.GroupePersonnes" 
        init-method="init" destroy-method="close">
        <property name="membres">
            <list>
                <ref bean="personne1"/>
                <ref bean="personne2"/>
            </list>
        </property>
        <property name="groupesDeTravail">
            <map>
                <entry key="Brigitte">
                    <value>Marketing</value>
                </entry>
                <entry key="Simon">
                    <value>Ressources humaines</value>
                </entry>
            </map>
        </property>
    </bean>
  1. La etiqueta <list> permite inicializar un campo de tipo matriz o que implemente la interfaz List con diferentes valores.
  2. La etiqueta <map> permite hacer lo mismo con un campo que implemente la interfaz Map

Para nuestras pruebas, utilizaremos la clase de prueba JUnit ya presentada, añadiéndole el siguiente método [test3]:

1
2
3
4
5
  public void test3() {
    // recuperación del bean [groupe1]
    GroupePersonnes groupe1 = (GroupePersonnes) bf.getBean("groupe1");
    System.out.println("groupe1=" + groupe1.toString());
  }

El método [test3] recupera el bean [groupe1] y lo muestra.

La estructura del proyecto de Eclipse sigue siendo la misma que en la prueba anterior. Al ejecutar el método [test3] de la prueba JUnit se obtienen los siguientes resultados:

18 sept. 2004 15:51:45 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
18 sept. 2004 15:51:45 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'groupe1'
18 sept. 2004 15:51:45 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne1'
init personne [nom=[Simon], age=[40]]
18 sept. 2004 15:51:45 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne2'
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}

Comentarios:

  • el método [test3] solicita una referencia del bean [groupe1]
  • línea 4: Spring inicia la creación de este bean
  • dado que el bean [groupe1] hace referencia a los beans [personne1] y [personne2], estos dos beans se crean (líneas 6 y 9) y se ejecuta su método init (líneas 7 y 10)
  • línea 11: se ha creado el bean [groupe1]. Ahora se ejecuta su método [init].
  • línea 12: visualización solicitada por el método [test3].

2.4. Spring para configurar aplicaciones web de tres capas

2.4.1. Arquitectura general de la aplicación

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

  • Las tres capas serán independientes gracias al uso de interfaces Java
  • La integración de las tres capas se llevará a cabo mediante Spring
  • Se crearán paquetes separados para cada una de las tres capas, que se llamarán Control, Domain y Dao. Un paquete adicional contendrá las aplicaciones de prueba.

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

Image

2.4.2. La capa DAO de acceso a datos

La capa DAO implementará la siguiente interfaz:

package istia.st.demo.dao;

public interface IDao1 {
  public int doSometingInDaoLayer(int a, int b);
}
  • Escriba dos clases, Dao1Impl1 y Dao1Impl2, que implementen la interfaz IDao1. El método Dao1Impl1. doSomethingInDaoLayer devolverá a+b y el método Dao1Impl2. doSomethingInDaoLayer devolverá a-b.
  • escribir una clase de prueba JUnit que pruebe las dos clases anteriores

2.4.3. La capa de negocio

La capa de negocio implementará la siguiente interfaz:

package istia.st.demo.domain;

public interface IDomain1 {
  public int doSomethingInDomainLayer(int a, int b);
}
  • Escriba dos clases, Domain1Impl1 y Domain1Impl2, que implementen la interfaz IDomain1. Estas clases tendrán un constructor que reciba como parámetro un objeto de tipo IDao1. El método Domain1Impl1.doSomethingInDomainLayer incrementará a y b en una unidad y luego pasará estos dos parámetros al método doSomethingInDaoLayer del objeto de tipo IDao1 recibido. Por su parte, el método Domain1Impl2.doSomethingInDomainLayer disminuirá a y b en una unidad antes de hacer lo mismo.
  • Escribir una clase de prueba JUnit que pruebe las dos clases anteriores

2.4.4. La capa de interfaz de usuario

La capa de interfaz de usuario implementará la siguiente interfaz:

package istia.st.demo.control;

public interface IControl1 {
  public int doSometingInControlLayer(int a, int b);
}
  • Escriba dos clases, Control1Impl1 y Control1Impl2, que implementen la interfaz IControl1. Estas clases tendrán un constructor que reciba un parámetro de tipo IDomain1. El método Control1Impl1.doSomethingInControlLayer incrementará a y b en una unidad y luego pasará estos dos parámetros al método doSomethingInDomainLayer del objeto de tipo IDomain1 recibido. El método Control11Impl2.doSomethingInControlLayer, por su parte, decrementará a y b en una unidad antes de hacer lo mismo.
  • Escribir una clase de prueba JUnit que pruebe las dos clases anteriores

2.4.5. Integración con Spring

  • Escribir un archivo de configuración de Spring que determinará qué clases deberá utilizar cada una de las tres capas anteriores
  • Escribir una clase de prueba JUnit que utilice diferentes configuraciones de Spring, con el fin de resaltar la flexibilidad de la aplicación desarrollada
  • Escribir una aplicación independiente (método main) que pase dos parámetros a la interfaz IControl1 y muestre el resultado generado por la interfaz.

2.4.6. Una solución

2.4.6.1. El proyecto Eclipse

Image

Los archivos de la carpeta [lib] se han agregado a [ClassPath] del proyecto.

2.4.6.2. El paquete [istia.st.demo.dao]

La interfaz:

1
2
3
4
5
6
7
8
9
package istia.st.demo.dao;

/**
 * @author ST-ISTIA
 *  
 */
public interface IDao1 {
  public int doSometingInDaoLayer(int a, int b);
}

Una primera clase de implementación:

package istia.st.demo.dao;

/**
 * @author ST-ISTIA
 *  
 */
public class Dao1Impl1 implements IDao1 {

     // se hace algo en la capa [dao]
  public int doSometingInDaoLayer(int a, int b) {
    return a+b;
  }

}

Una segunda clase de implementación:

package istia.st.demo.dao;

/**
 * @author ST-ISTIA
 *
 */
public class Dao1Impl2 implements IDao1 {

     // se hace algo en la capa [dao]
  public int doSometingInDaoLayer(int a, int b) {
    return a-b;
  }
}

2.4.6.3. El paquete [istia.st.demo.domain]

La interfaz:

package istia.st.demo.domain;

/**
 * @author ST-ISTIA
 *  
 */
public interface IDomain1 {

     // se está haciendo algo en la capa [domain]
  public int doSomethingInDomainLayer(int a, int b);
}

Una primera clase de implementación:

package istia.st.demo.domain;

import istia.st.demo.dao.IDao1;

/**
 * @author ST-ISTIA
 *  
 */
public class Domain1Impl1 implements IDomain1 {

     // el servicio de acceso a la capa [dao]
  private IDao1 dao1;

  public Domain1Impl1() {
     // constructor sin argumentos
  }

     // almacena el servicio de acceso a la capa [dao]
  public Domain1Impl1(IDao1 dao1) {
    this.dao1 = dao1;
  }

     // se realiza alguna acción en la capa [domain]
  public int doSomethingInDomainLayer(int a, int b) {
    a++;
    b++;
    return dao1.doSometingInDaoLayer(a, b);
  }
}

Una segunda clase de implementación:

package istia.st.demo.domain;

import istia.st.demo.dao.IDao1;

/**
 * @author ST-ISTIA
 *  
 */
public class Domain1Impl2 implements IDomain1 {

     // el servicio de acceso a la capa [dao]
  private IDao1 dao1;

  public Domain1Impl2() {
     // constructor sin argumentos
  }

     // almacena el servicio de acceso a la capa [dao]
  public Domain1Impl2(IDao1 dao1) {
    this.dao1 = dao1;
  }

     // se realiza alguna acción en la capa [domain]
  public int doSomethingInDomainLayer(int a, int b) {
    a--;
    b--;
    return dao1.doSometingInDaoLayer(a, b);
  }
}

2.4.6.4. El paquete [istia.st.demo.control]

La interfaz

1
2
3
4
5
6
7
8
9
package istia.st.demo.control;

/**
 * @author ST-ISTIA
 *  
 */
public interface IControl1 {
  public int doSometingInControlLayer(int a, int b);
}

Una primera clase de implementación:

package istia.st.demo.control;

import istia.st.demo.domain.IDomain1;

/**
 * @author ST-ISTIA
 *  
 */
public class Control1Impl1 implements IControl1 {
   // clase de negocio en la capa [domain]
    private IDomain1 domain1;

  public Control1Impl1() {
     // constructor sin argumentos
  }

     // memorización del servicio de acceso a la capa [domain]
  public Control1Impl1(IDomain1 domain1) {
    this.domain1 = domain1;
  }

     // se hace algo
  public int doSometingInControlLayer(int a, int b) {
    a++;
    b++;
    return domain1.doSomethingInDomainLayer(a, b);
  }

}

Una segunda clase de implementación:

package istia.st.demo.control;

import istia.st.demo.domain.IDomain1;

/**
 * @author ST-ISTIA
 *  
 */
public class Control1Impl2 implements IControl1 {

     // la clase de acceso a la capa [domain]
    private IDomain1 domain1;

  public Control1Impl2() {
     // constructor sin argumentos
  }

     // almacena la clase de acceso a la capa [domain]
  public Control1Impl2(IDomain1 domain1) {
    this.domain1 = domain1;
  }

     // se realiza alguna acción
  public int doSometingInControlLayer(int a, int b) {
    a--;
    b--;
    return domain1.doSomethingInDomainLayer(a, b);
  }

}

2.4.6.5. Los archivos de configuración [Spring]

Un primer archivo [springMainTest1.xml]:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- la clase DAO -->
    <bean id="dao" class="istia.st.demo.dao.Dao1Impl1">
    </bean>
     <!-- la clase de negocio -->
    <bean id="domain" class="istia.st.demo.domain.Domain1Impl1">
        <constructor-arg index="0">
            <ref bean="dao"/>
        </constructor-arg>
    </bean>
     <!-- la clase de control -->
    <bean id="control" class="istia.st.demo.control.Control1Impl1">
        <constructor-arg index="0">
            <ref bean="domain"/>
        </constructor-arg>
    </bean>
</beans>

Un segundo [springMainTest2.xml]:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- la clase DAO -->
    <bean id="dao" class="istia.st.demo.dao.Dao1Impl2">
    </bean>
     <!-- la clase de negocio -->
    <bean id="domain" class="istia.st.demo.domain.Domain1Impl2">
        <constructor-arg index="0">
            <ref bean="dao"/>
        </constructor-arg>
    </bean>
     <!-- la clase de control -->
    <bean id="control" class="istia.st.demo.control.Control1Impl2">
        <constructor-arg index="0">
            <ref bean="domain"/>
        </constructor-arg>
    </bean>
</beans>

2.4.6.6. El paquete de pruebas [istia.st.demo.tests]

Una prueba de tipo [main]:

package istia.st.demo.tests;

import istia.st.demo.control.IControl1;

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

/**
 * @author ST-ISTIA
 *  
 */
public class MainTest1 {
  public static void main(String[] arguments) {
     // se obtiene una implementación de la interfaz IControl1
    IControl1 control = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest1.xml"))).getBean("control");
     // se utiliza la clase
    int a = 10, b = 20;
    int res = control.doSometingInControlLayer(a, b);
     // se muestra el resultado
    System.out.println("control(" + a + "," + b + ")=" + res);
  }
}

Los resultados obtenidos en la consola de Eclipse:

11 mars 2005 11:25:14 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [springMainTest1.xml]
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'control'
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'domain'
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'dao'
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'domain' instantiated via constructor [public istia.st.demo.domain.Domain1Impl1(istia.st.demo.dao.IDao1)]
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'control' instantiated via constructor [public istia.st.demo.control.Control1Impl1(istia.st.demo.domain.IDomain1)]
control(10,20)=34

Otra prueba que utiliza el segundo archivo de configuración [Spring]:

package istia.st.demo.tests;

import istia.st.demo.control.IControl1;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

/**
 * @author ST-ISTIA
 *  
 */
public class MainTest2 {
  public static void main(String[] arguments) {
     // se obtiene una implementación de la interfaz IControl1
    IControl1 control = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest2.xml"))).getBean("control");
     // se utiliza la clase
    int a = 10, b = 20;
    int res = control.doSometingInControlLayer(a, b);
     // se muestra el resultado
    System.out.println("control(" + a + "," + b + ")=" + res);
  }
}

Los resultados obtenidos en la consola de Eclipse:

11 mars 2005 11:28:52 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [springMainTest2.xml]
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'control'
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'domain'
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'dao'
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'domain' instantiated via constructor [public istia.st.demo.domain.Domain1Impl2(istia.st.demo.dao.IDao1)]
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'control' instantiated via constructor [public istia.st.demo.control.Control1Impl2(istia.st.demo.domain.IDomain1)]
control(10,20)=-10

Por último, una prueba con JUnit:

package istia.st.demo.tests;

import istia.st.demo.control.IControl1;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import junit.framework.TestCase;

/**
 * @author ST-ISTIA
 *  
 */
public class JunitTest2Control1 extends TestCase {
  public void testControl1() {
     // se recupera una implementación de la interfaz IControl1
    IControl1 control1 = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest1.xml"))).getBean("control");
     // se utiliza la clase
    int a1 = 10, b1 = 20;
    int res1 = control1.doSometingInControlLayer(a1, b1);
    assertEquals(34, res1);
     // se recupera otra implementación de la interfaz IControl1
    IControl1 control2 = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest2.xml"))).getBean("control");
     // se utiliza la clase
    int a2 = 10, b2 = 20;
    int res2 = control2.doSometingInControlLayer(a2, b2);
    assertEquals(-10, res2);
  }
}

2.5. Conclusion

El marco Spring ofrece una gran flexibilidad tanto en la arquitectura de las aplicaciones como en su configuración. Hemos utilizado el concepto IoC, uno de los dos pilares de Spring. El otro pilar es AOP (programación orientada a aspectos), que no hemos presentado. Permite agregar, mediante configuración, «comportamiento» a un método de clase sin modificar el código de esta. Esquemáticamente, AOP permite filtrar las llamadas a ciertos métodos:

  • el filtro puede ejecutarse antes o después del método M de destino, o en ambos casos.
  • El método M ignora la existencia de estos filtros. Estos se definen en el archivo de configuración de Spring.
  • El código del método M no se modifica. Los filtros son clases de Java que deben crearse. Spring proporciona filtros predefinidos, en particular para gestionar las transacciones de SGBD.
  • Los filtros son beans y, como tales, se definen en el archivo de configuración de Spring como beans.

Un filtro común es el filtro transaccional. Tomemos un método M de la capa de negocio que realiza dos operaciones inseparables sobre los datos (unidad de trabajo). Este método utiliza dos métodos, M1 y M2, de la capa DAO para llevar a cabo estas dos operaciones.

Dado que se encuentra en la capa de negocio, el método M no tiene en cuenta el soporte de estos datos. Por ejemplo, no tiene que los datos se encuentren en un SGBD ni que deba incluir las dos llamadas a los métodos M1 y M2 dentro de una transacción de SGBD. Es la capa DAO la que debe encargarse de estos detalles. Una solución al problema anterior es, entonces, crear un método en la capa DAO que, a su vez, llame a los métodos M1 y M2, llamadas que incluiría en una transacción de SGBD.

La solución de filtrado AOP es más flexible. Permitirá definir un filtro que, antes de llamar a M, iniciará una transacción y, después de la llamada, realizará un commit o un rollback, según corresponda.

Este enfoque presenta varias ventajas:

  • una vez definido el filtro, se puede aplicar a varios métodos, por ejemplo, a todos aquellos que requieran una transacción
  • los métodos así filtrados no tienen que reescribirse
  • los filtros que se utilizarán se definen mediante configuración, por lo que se pueden modificar

Además de los conceptos IoC y AOP, Spring ofrece numerosas clases de apoyo para aplicaciones de tres capas:

  • para JDBC, SqlMap (iBATIS), Hibernate, JDO (Java Data Object) en la capa DAO
  • para el modelo MVC en la capa de interfaz de usuario

Para más información: http://www.springframework.org.