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:

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

La ejecución del método [demo] de la prueba NUnit da los resultados indicados anteriormente y que se recogen 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]. 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.
- línea 4: la operación
ha mostrado el valor del objeto [Personne] creado.
- líneas 5-6: se repite el mismo fenómeno 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 ha dado una línea de visualización:
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
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:
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:

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

Comentemos las pantallas:
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>
- La etiqueta <list> permite inicializar con diferentes valores un campo de tipo matriz 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 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.

La ejecución del método [demo3] de la prueba NUnit da 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 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]