Skip to content

5. Spring IoC en la práctica

Ahora abordaremos, con ejemplos, la aplicación práctica de lo que hemos visto anteriormente.

5.1. Ejemplo 1

La clase [Personne.vb] es la siguiente:

Namespace istia.st.springioc.demos
    Public Class Personne

         ' campos privados
        Private _nom As String
        Private _age As Integer

         ' constructor por defecto
        Public Sub New()
        End Sub

         ' propiedades asociadas a los campos privados
        Public Property nom() As String
            Get
                Return _nom
            End Get
            Set(ByVal Value As String)
                _nom = Value
            End Set
        End Property

        Public Property age() As Integer
            Get
                Return _age
            End Get
            Set(ByVal Value As Integer)
                _age = Value
            End Set
        End Property

         ' cadena de identidad
        Public Overrides Function tostring() As String
            Return String.Format("[{0},{1}]", nom, age)
        End Function

         ' método init
        Public Sub init()
            Console.WriteLine("init personne {0}", Me.ToString)
        End Sub

         ' método close
        Public Sub close()
            Console.WriteLine("destroy personne {0}", Me.ToString)
        End Sub

    End Class
End Namespace

La clase presenta:

  • dos campos privados «nombre» y «edad» accesibles a través de propiedades
  • un método toString para recuperar el valor del objeto [Personne] en forma de cadena de caracteres
  • un método init que será invocado por Spring al crear el objeto, un método close que será invocado al destruir el objeto

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

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">

<objects>
    <object id="personne1" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
        destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </object>
    <object id="personne2" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
        destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </object>
</objects>

Este archivo

  • define dos objetos de claves respectivos «persona1» y «persona2» de tipo [Personne]
  • inicializa las propiedades [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 clases de prueba NUnit. La primera será la siguiente:

Imports System
Imports Spring.Objects.Factory.Xml
Imports System.IO
Imports NUnit.Framework
Imports istia.st.springioc.demos

Namespace istia.st.springioc.tests

    <TestFixture()> _
    Public Class NunitTestSpringIocDemo1
         ' el objeto a probar
        Private factory As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
             ' se crea una instancia de factory
            factory = New XmlObjectFactory(New FileStream("spring-config-1.xml", FileMode.Open))
            ' log
            Console.WriteLine("setup test")
        End Sub

        <Test()> _
        Public Sub demo()
             ' recuperación de los objetos [Personne] del archivo Spring mediante su clave
            Dim personne1 As Personne = CType(factory.GetObject("personne1"), Personne)
            Console.WriteLine("personne1=" + personne1.ToString())
            Dim personne2 As Personne = CType(factory.GetObject("personne2"), Personne)
            Console.WriteLine("personne2=" + personne2.ToString())
            personne2 = CType(factory.GetObject("personne2"), Personne)
            Console.WriteLine("personne2=" + personne2.ToString())
        End Sub

        <TearDown()> _
        Public Sub destroy()
             ' se destruyen los singletons
            factory.Dispose()
             ' se libera el recurso de la fábrica
            factory = Nothing
            ' suivi
            Console.WriteLine("teardown test")
        End Sub

    End Class
End Namespace

Comentarios:

  • para obtener los objetos definidos en el archivo [spring-config-1.xml], utilizamos un objeto de tipo [XmlObjectFactory]. Existen otros tipos de «fábrica» que permiten acceder a los singletons del archivo de configuración. El objeto [XmlObjectFactory] se obtiene en el método de atributo [SetUp] de la clase de prueba y se almacena en una variable privada. Recordemos que el método dotado del atributo <Setup()> se ejecuta antes de cada prueba.
  • El archivo [spring-config-1.xml] se colocará en la carpeta [bin] de la aplicación
  • Spring puede utilizar archivos de configuración con diversos formatos. El objeto [XmlObjectFactory] permite analizar un archivo de configuración en formato XML.
  • La evaluación de un archivo Spring devuelve un objeto de tipo [XmlObjectFactory], en este caso el objeto factory. Con este objeto, se obtiene un singleton identificado por la clave C mediante factory.getObject(C).
  • El método [demo] solicita y muestra el valor de los singletons con las claves «personne1» y «personne2».

La estructura del proyecto de Visual Studio es la siguiente:

Image

Comentarios:

  • el proyecto VS se llama [demo1]
  • la carpeta [istia] contiene los códigos fuente. Los códigos compilados se guardarán en la carpeta [bin] dentro de DLL [demo1.dll]:

Image

  • El archivo [spring-config-1.xml] se encuentra en la carpeta [bin] del proyecto.
  • La carpeta [bin] contiene los archivos necesarios para la aplicación:
    • Spring.Core.dll, Spring.Core.xml para las clases de Spring
    • log4net.dll para los registros de Spring
    • demo1.dll, que es el DLL generado por la generación del proyecto

El archivo DLL [demo1.dll] generado por el proyecto se carga en la herramienta de pruebas gráficas [Nunit-Gui 2.2]. Esta muestra automáticamente las clases Nunit presentes en el archivo DLL:

Image

La ejecución del método [demo] de la prueba NUnit da los resultados indicados anteriormente y que se recogen a continuación:

***** istia.st.springioc.tests.NunitTestSpringIocDemo1.demo
setup test
init personne [Simon,40]
personne1=[Simon,40]
init personne [Brigitte,20]
personne2=[Brigitte,20]
personne2=[Brigitte,20]
destroy personne [Simon,40]
destroy personne [Brigitte,20]
teardown test

Comentarios:

  • línea 2: la prueba comienza con la ejecución del método de atributo <Setup()>
  • línea 3: la operación
            Dim personne1 As Personne = CType(factory.GetObject("personne1"), Personne)

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

init personne [Simon,40]
  • línea 4: la operación
            Console.WriteLine("personne1=" + personne1.ToString())

ha mostrado el valor del objeto [Personne] creado.

personne1=[Simon,40]
  • líneas 5-6: se repite el mismo fenómeno para el bean de clave [personne2].
init personne [Brigitte,20]
personne2=[Brigitte,20]
  • línea 7: la última operación
personne2 = CType(factory.GetObject("personne2"), Personne)
Console.WriteLine("personne2=" + personne2.ToString())

solo ha dado una línea de visualización:

personne2=[Brigitte,20]

Por lo tanto, 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], lo cual no ocurre aquí. 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 de objetos. Si se le solicita la referencia de un objeto aún no creado, lo crea y devuelve una referencia. Si el objeto ya ha sido creado, Spring se limita a proporcionar una referencia.

  • líneas 8-10: se ejecuta el método de atributo <TearDown()> (línea 10). En su interior, la operación
             ' se destruyen los singletons
            factory.Dispose()

provoca la destrucción de los singletons creados por Spring. Esto provoca que, para cada uno de ellos, se ejecute su método [close], ya que se había escrito, en el archivo de configuración, para cada uno de ellos: «destroy-method=close». Esto es lo que indican las salidas:

destroy personne [Simon,40]
destroy personne [Brigitte,20]

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

5.2. Ejemplo 2

Consideremos la siguiente nueva clase [Voiture]:

Imports istia.st.springioc.demos

Namespace istia.st.springioc.demos

    Public Class Voiture
         ' campos privados
        Private _marque As String
        Private _type As String
        Private _propriétaire As Personne

         ' propiedades públicas
        Public Property marque() As String
            Get
                Return _marque
            End Get
            Set(ByVal Value As String)
                _marque = Value
            End Set
        End Property

        Public Property type() As String
            Get
                Return _type
            End Get
            Set(ByVal Value As String)
                _type = Value
            End Set
        End Property

        Public Property propriétaire() As Personne
            Get
                Return _propriétaire
            End Get
            Set(ByVal Value As Personne)
                _propriétaire = Value
            End Set
        End Property

         ' constructor por defecto
        Public Sub New()

        End Sub

         ' constructor con tres parámetros
        Public Sub New(ByVal marque As String, ByVal type As String, ByVal propriétaire As Personne)
             ' se inicializan los campos privados a través de sus propiedades asociadas
            With Me
                .marque = marque
                .type = type
                .propriétaire = propriétaire
            End With
        End Sub

         ' cadena de identidad del objeto Coche
        Public Overrides Function tostring() As String
            Return String.Format("[{0},{1},{2}]", marque, type, propriétaire.ToString)
        End Function

         ' métodos init-destroy
        Public Sub init()
            Console.WriteLine("init voiture {0}", Me.ToString)
        End Sub

        Public Sub destroy()
            Console.WriteLine("destroy voiture {0}", Me.ToString)
        End Sub

    End Class
End Namespace

La clase presenta:

  • tres campos privados _type, _marque y _propriétaire. Estos campos pueden inicializarse y leerse mediante propiedades públicas. También pueden inicializarse utilizando el constructor Coche(String, String, Persona). La clase también cuenta con un constructor sin argumentos que permite crear primero un objeto [Voiture] no inicializado y, a continuación, inicializarlo a través de sus propiedades públicas.
  • un método toString para recuperar el valor del objeto [Voiture] en forma de cadena de caracteres
  • un método init que será invocado por Spring justo después de la creación del objeto, un método destroy que será invocado al destruir el objeto

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

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
     <!-- personas -->
    <object id="personne1" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
        destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </object>
    <object id="personne2" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
        destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </object>
     <!-- un coche -->
    <object id="voiture1" type="istia.st.springioc.demos.Voiture, demo1" init-method="init"
        destroy-method="destroy">
        <constructor-arg index="0">
            <value>Peugeot</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>307</value>
        </constructor-arg>
        <constructor-arg index="2">
            <ref object="personne2"/>
        </constructor-arg>
    </object>
</objects>

Este archivo añade a las definiciones anteriores un objeto de clave «voiture1» de tipo [Voiture]. Para inicializar este objeto, se podría haber escrito:

    <object id="voiture1" type="istia.st.springioc.demos.Voiture, demo1" init-method="init"
        destroy-method="destroy">
        <constructor-arg index="0">
            <value>Peugeot</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>307</value>
        </constructor-arg>
        <constructor-arg index="2">
            <ref object="personne2"/>
        </constructor-arg>
    </object>

En lugar de optar por este método ya presentado, hemos decidido aquí utilizar el constructor Carro(String, String, Persona) de la clase. Por otra parte, el singleton [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 siguiente clase de prueba NUnit [NunitTestSpringIocDemo2]:

Imports System
Imports Spring.Objects.Factory.Xml
Imports System.IO
Imports NUnit.Framework
Imports istia.st.springioc.demos

Namespace istia.st.springioc.tests

    <TestFixture()> _
    Public Class NunitTestSpringIocDemo2
         ' la fábrica de singletons
        Private factory As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
             ' se crea una instancia de la fábrica
            factory = New XmlObjectFactory(New FileStream("spring-config-2.xml", FileMode.Open))
            ' log
            Console.WriteLine("setup test")
        End Sub

        <TearDown()> _
    Public Sub destroy()
             ' se destruyen los singletons
            factory.Dispose()
             ' se libera la fábrica
            factory = Nothing
            ' suivi
            Console.WriteLine("teardown test")
        End Sub

        <Test()> _
        Public Sub demo2()
             ' recuperación del singleton [voiture1]
            Dim voiture1 As Voiture = CType(factory.GetObject("voiture1"), Voiture)
            Console.WriteLine("Voiture1=[{0}]", voiture1)
        End Sub

    End Class
End Namespace

Comentarios:

  • los métodos de atributos <Setup()> y <TearDown()> permanecen sin cambios.
  • El método [demo2] recupera una referencia al singleton [voiture1] y lo muestra.

La estructura del proyecto de Visual Studio sigue siendo la misma que antes. Solo han aparecido dos nuevas clases, así como un archivo de configuración de Spring:

Image

La ejecución de la prueba NUnit da los siguientes resultados:

Image

Comentemos las pantallas:

1
2
3
4
5
6
7
8
***** istia.st.springioc.tests.NunitTestSpringIocDemo2.demo2
setup test
init personne [Brigitte,20]
init voiture [Peugeot,307,[Brigitte,20]]
Voiture1=[[Peugeot,307,[Brigitte,20]]]
destroy voiture [Peugeot,307,[Brigitte,20]]
destroy personne [Brigitte,20]
teardown test

Comentarios:

  • línea 2: método de atributo [Setup] ejecutado antes de cada prueba, en este caso [demo]
  • línea 3: Spring inicia la creación del singleton [voiture1]. Este tiene una dependencia del singleton [personne2], que no existe. Por lo tanto, se crea este último y se ejecuta su método [init].
  • línea 4: ahora se puede crear el singleton [voiture1]. A continuación, se ejecuta su método [init].
  • línea 5: el método [demo2] muestra el valor del singleton [voiture1]
  • líneas 6-8: se ejecuta el método [TearDown] de la prueba. Los singletons se destruyen, lo que da lugar a la ejecución de sus métodos [destroy]

5.3. Ejemplo 3

Introducimos la siguiente nueva clase [GroupePersonnes]:

Imports istia.st.springioc.demos

Namespace istia.st.springioc.demos

    Public Class GroupePersonnes
         ' campos privados
        Private _membres() As Personne
        Private _groupesDeTravail As Hashtable

         ' propiedades públicas
        Public Property membres() As Personne()
            Get
                Return _membres
            End Get
            Set(ByVal Value() As Personne)
                _membres = Value
            End Set
        End Property

        Public Property groupesDeTravail() As Hashtable
            Get
                Return _groupesDeTravail
            End Get
            Set(ByVal Value As Hashtable)
                _groupesDeTravail = Value
            End Set
        End Property

         ' constructor por defecto
        Public Sub New()

        End Sub

         ' cadena de identidad del objeto GroupeDePersonnes
        Public Overrides Function tostring() As String
             ' se recorre la lista de miembros del grupo
            Dim identité As String = "[membres=("
            Dim i As Integer
            For i = 0 To membres.Length - 2
                identité += membres(i).ToString + ","
            Next
            identité += membres(i).ToString + "), groupes de travail=("
             ' se recorre el diccionario de grupos de trabajo
            Dim clés As IEnumerator = groupesDeTravail.Keys.GetEnumerator
            Dim clé As Object
            While clés.MoveNext
                clé = clés.Current
                identité += String.Format("[{0},{1}] ", clé, groupesDeTravail(clé))
            End While
             ' se devuelve el resultado
            Return identité + "]"
        End Function

         ' métodos init-destroy
        Public Sub init()
            Console.WriteLine("init GroupeDePersonnes {0}", Me.ToString)
        End Sub

        Public Sub destroy()
            Console.WriteLine("destroy GroupeDePersonnes {0}", Me.ToString)
        End Sub

    End Class
End Namespace

Sus dos miembros privados son:

_membres: una tabla de personas que forman parte del grupo

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

Se puede acceder a estos miembros privados a través de propiedades públicas. El objetivo aquí es mostrar cómo Spring permite inicializar objetos complejos, como los que tienen campos de tipo matriz o diccionario.

El archivo de configuración de Spring [spring-config-3.xml] será el siguiente:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
     <!-- de personas -->
    <object id="personne1" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
        destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </object>
    <object id="personne2" type="istia.st.springioc.demos.Personne, demo1" init-method="init"
        destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </object>
     <!-- un coche -->
    <object id="voiture1" type="istia.st.springioc.demos.Voiture, demo1" init-method="init"
        destroy-method="destroy">
        <constructor-arg index="0">
            <value>Peugeot</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>307</value>
        </constructor-arg>
        <constructor-arg index="2">
            <ref object="personne2" />
        </constructor-arg>
    </object>
     <!-- un grupo de personas -->
    <object id="groupe1" type="istia.st.springioc.demos.GroupePersonnes" init-method="init"
        destroy-method="destroy">
        <property name="membres">
            <list>
                <ref object="personne1" />
                <ref object="personne2" />
            </list>
        </property>
        <property name="groupesDeTravail">
            <dictionary>
                <entry key="Brigitte">
                    <value>Marketing</value>
                </entry>
                <entry key="Simon">
                    <value>Ressources humaines</value>
                </entry>
            </dictionary>
        </property>
    </object>
</objects>
  1. La etiqueta <list> permite inicializar con diferentes valores un campo de tipo matriz o de tipo IList.
  2. La etiqueta <dictionary> permite hacer lo mismo con un campo que implemente la interfaz IDictionary

Para nuestras pruebas, utilizaremos la siguiente clase de prueba NUnit [NunitTestSpringIocDemo3]:

Imports System
Imports Spring.Objects.Factory.Xml
Imports System.IO
Imports NUnit.Framework
Imports istia.st.springioc.demos

Namespace istia.st.springioc.tests

    <TestFixture()> _
    Public Class NunitTestSpringIocDemo3
         ' el objeto a probar
        Private factory As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
             ' se crea una instancia de fábrica
            factory = New XmlObjectFactory(New FileStream("spring-config-3.xml", FileMode.Open))
            ' log
            Console.WriteLine("setup test")
        End Sub

        <TearDown()> _
        Public Sub destroy()
             ' se destruyen los singletons
            factory.Dispose()
            ' suivi
            Console.WriteLine("teardown test")
        End Sub

        <Test()> _
    Public Sub demo3()
             ' recuperación del singleton [groupe1]
            Dim groupe1 As GroupePersonnes = CType(factory.GetObject("groupe1"), GroupePersonnes)
            Console.WriteLine("groupe1={0}", groupe1)
        End Sub

    End Class
End Namespace

El método [demo3] recupera el singleton [groupe1] y lo muestra.

La estructura del proyecto de Visual Studio sigue siendo la misma que antes, salvo que ahora tiene dos clases y un archivo de configuración adicionales.

Image

La ejecución del método [demo3] de la prueba NUnit da los siguientes resultados:

Image

Comentarios:

***** istia.st.springioc.tests.NunitTestSpringIocDemo3.demo3
setup test
init personne [Simon,40]
init personne [Brigitte,20]
init GroupeDePersonnes [membres=([Simon,40],[Brigitte,20]), groupes de travail=([Brigitte,Marketing] [Simon,Ressources humaines] ]
groupe1=[membres=([Simon,40],[Brigitte,20]), groupes de travail=([Brigitte,Marketing] [Simon,Ressources humaines] ]
destroy GroupeDePersonnes [membres=([Simon,40],[Brigitte,20]), groupes de travail=([Brigitte,Marketing] [Simon,Ressources humaines] ]
destroy personne [Simon,40]
destroy personne [Brigitte,20]
teardown test
  • línea 2: método de atributo [Setup] ejecutado antes de cada prueba, en este caso [demo]
  • líneas 3-4: Spring inicia la creación del singleton [groupe1]. Este tiene una dependencia respecto a los singletons [personne1, personne2] que no existen. Estos últimos se crean y se ejecutan sus métodos [init].
  • línea 5: ahora se puede crear el singleton [groupe1]. A continuación, se ejecuta su método [init].
  • línea 6: el método [demo3] muestra el valor del singleton [groupe1]
  • líneas 7-10: se ejecuta el método [TearDown] de la prueba. Los singletons se destruyen, lo que da lugar a la ejecución de sus métodos [destroy]