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», a los que se puede acceder 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 Spring llamará al crear el objeto, y un métodoclose** que se llamará al destruir el objeto
Para crear objetos de tipo [Personne], utilizaremos el siguiente archivo de 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 denominados «persona1» y «persona2», respectivamente, del 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 la fábrica
factory = New XmlObjectFactory(New FileStream("spring-config-1.xml", FileMode.Open))
' registro
Console.WriteLine("setup test")
End Sub
<Test()> _
Public Sub demo()
' se recuperan 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
' seguimiento
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ábricas» 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 con el 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 en diversos formatos. El objeto [XmlObjectFactory] permite analizar un archivo de configuración en formato XML.
- Al procesar un archivo de Spring se obtiene 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 «persona1» y «persona2».
La estructura del proyecto de Visual Studio es la siguiente:

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]:

- 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 archivo DLL generado al crear el proyecto
El archivo DLL ([demo1.dll]), generado al crear el proyecto, se carga en la herramienta de pruebas gráficas [Nunit-Gui 2.2]. Esta herramienta muestra automáticamente las clases NUnit presentes en el archivo DLL:

La ejecución del método [demo] de la prueba NUnit arroja los resultados indicados anteriormente y que se muestran a continuación:
Comentarios:
- línea 2: la prueba comienza con la ejecución del método de atributo <Setup()>
- línea 3: la operación
ha forzado la creación del bean [personne1]. Debido a 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.
- línea 4: la operación
ha mostrado el valor del objeto [Personne] creado.
- líneas 5-6: el mismo fenómeno se repite para el bean de clave [personne2].
- línea 7: la última operación
personne2 = CType(factory.GetObject("personne2"), Personne)
Console.WriteLine("personne2=" + personne2.ToString())
solo generó una línea en pantalla:
Por lo tanto, 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.
- líneas 8-10: se ejecuta el método de atributo <TearDown()> (línea 10). En su interior, la operación
provoca la destrucción de los singletons creados por Spring. Esto hace que, para cada uno de ellos, se ejecute su método [close], ya que en el archivo de configuración se había escrito, para cada uno: «destroy-method=close». Esto es lo que indican las salidas:
Ahora que ya tenemos 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 clase nueva [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 Auto
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 Auto(String, String, Persona). La clase también cuenta con un constructor sin argumentos que permite crear primero un objeto [Voiture] no inicializado y luego inicializarlo mediante 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 Spring llamará inmediatamente después de la creación del objeto, y un métododestroy**que se llamará 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 auto -->
<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 agrega a las definiciones anteriores un objeto de clave «auto1» 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, aquí hemos decidido utilizar el constructor Carro(String, String, Persona) de la clase. Además, 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))
' registro
Console.WriteLine("setup test")
End Sub
<TearDown()> _
Public Sub destroy()
' se destruyen los singletons
factory.Dispose()
' se libera la fábrica
factory = Nothing
' seguimiento
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] obtiene 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:

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

Comentemos lo que se ve en pantalla:
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 clase nueva [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 son miembros del grupo
_groupesDeTravail: un diccionario que asigna a 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 aquellos que contienen 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 auto -->
<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>
- La etiqueta <list> permite inicializar con diferentes valores un campo de tipo tabla o de tipo IList.
- 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 la fábrica
factory = New XmlObjectFactory(New FileStream("spring-config-3.xml", FileMode.Open))
' registro
Console.WriteLine("setup test")
End Sub
<TearDown()> _
Public Sub destroy()
' se destruyen los singletons
factory.Dispose()
' seguimiento
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 cuenta con dos clases y un archivo de configuración adicionales.

Al ejecutar el método [demo3] de la prueba NUnit se obtienen los siguientes resultados:

Comentarios:
- 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 de 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]