Skip to content

2. Configurar una aplicación con Spring

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

Supondremos que el acceso a la capa DAO está controlado por una interfaz [IArticlesDao]:

....
Namespace istia.st.articles.dao

    Public Interface IArticlesDao
         ' lista de todos los artículos
        Function getAllArticles() As IList
         ' agrega un artículo
        Function ajouteArticle(ByVal unArticle As Article) As Integer
         ' elimina un artículo
        Function supprimeArticle(ByVal idArticle As Integer) As Integer
         ' modifica un artículo
        Function modifieArticle(ByVal unArticle As Article) As Integer
         ' busca un artículo
        Function getArticleById(ByVal idArticle As Integer) As Article
         ' elimina todos los artículos
        Sub clearAllArticles()
         ' inserta artículos en una transacción
        Sub doInsertionsInTransaction(ByVal articles As Article())
         ' modifica el inventario de un artículo
        Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
    End Interface
End Namespace

En la capa de acceso a datos o capa DAO (Data Access Object), es común trabajar con un SGBD. Consideremos el caso en el que se accede a este a través de un controlador ODBC. El esqueleto de una clase que acceda a esta fuente ODBC podría ser el siguiente:

NameSpace istia.st.articles.dao

Imports System.Data.Odbc
...
    Public Class ArticlesDaoPlainODBC
        Implements istia.st.articles.dao.IArticlesDao

         ' campos privados
        Private connexion As OdbcConnection = Nothing
        Private DSN As String

        Public Sub New(ByVal DSN As String, ByVal user As String, ByVal passwd As String)
             'recupera el nombre de la fuente ODBC
            Me.DSN = DSN
             ' se crea la cadena de conexión
            Dim connectString As String = String.Format("DSN={0};UID={1};PWD={2}", DSN, user, passwd)
             'se instancia la conexión
            connexion = New OdbcConnection(connectString)
        End Sub

....
    End Class
End NameSpace

Para realizar una operación en la fuente ODBC, cualquier método necesita un objeto [OdbcConnection] que represente la conexión a la base de datos a través de la cual transitarán los intercambios entre esta y la aplicación. Para crear este objeto, se necesitan tres datos:

DSN As String
el nombre de la fuente ODBC
user As String
la identidad con la que se crea la conexión
passwd As String
la contraseña asociada a esta identidad

Nuestra clase [ArticlesDaoPlainODBC] obtiene esta información a través del agente externo que instancia un miembro de la clase. Cabe preguntarse cómo este agente obtiene los tres datos necesarios para instanciar la clase [ArticlesDaoPlainODBC]. Veamos un ejemplo. Supongamos que queremos escribir una clase de prueba de la capa [Dao]. Tendríamos la siguiente arquitectura:

El esqueleto de una clase de prueba de Nunit [http://www.nunit.org/] podría ser el siguiente:

Imports System
Imports System.Collections
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports ArticlesDaoSqlmap = istia.st.articles.dao.ArticlesDaoSqlMap
Imports Article = istia.st.articles.domain.Article
Imports System.Threading

    <TestFixture()> _
    Public Class NunitTestArticlesDaoPlainOdbc

         ' el objeto a probar
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
             ' se crea una instancia del objeto a probar
            articlesDao = New ArticlesDaoPlainODBC("odbc-firebird-articles", "SYSDBA", "masterkey")
        End Sub

        <Test()> _
        Public Sub testGetAllArticles()
             ' verificación visual
            listArticles()
        End Sub

         ' captura de pantalla
        Private Sub listArticles()
            Dim articles As IList = articlesDao.getAllArticles
            For i As Integer = 0 To articles.Count - 1
                Console.WriteLine(CType(articles(i), Article).ToString)
            Next
        End Sub


    End Class

El entorno de pruebas Nunit es una adaptación a la plataforma .NET del entorno JUnit que existe para la plataforma Java. En la clase anterior, el método que tiene el atributo <SetUp()> se ejecuta antes de cada método de prueba. El que tiene el atributo <TearDown()>, por su parte, se ejecuta después de cada prueba. No hay ninguno en el ejemplo anterior. Aquí vemos que el método [init], que tiene el atributo <SetUp()>, instancia un objeto [ArticlesDaoPlainODBC] pasándole de forma «estática» los tres datos que necesita el constructor de ese objeto.

Nuestra clase de prueba queda a merced de cualquier cambio en uno de los datos codificados de forma estática. Sería preferible que estos se registraran en un archivo de configuración, para evitar recompilaciones innecesarias cuando cambien. La solución habitual para configurar una aplicación es utilizar un archivo donde se recojan todos los datos que puedan cambiar con el tiempo. 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 [ArticlesDaoPlainODBC] podría 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>
     <!-- la clase de implementación de la interfaz IArticlesDao -->
    <description>Gestion d'une table d'articles</description>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, articlesdao">
      <constructor-arg index="0">
            <value>odbc-firebird-articles</value>
        </constructor-arg>
         <constructor-arg index="1">
            <value>SYSDBA</value>
        </constructor-arg>
         <constructor-arg index="2">
            <value>masterkey</value>
        </constructor-arg>
    </object>
</objects>

El archivo de configuración de Spring describe los objetos que se deben instanciar. Por lo general, no especifica cuándo se instanciarán. El momento de su instanciación lo decide, entonces, el código que utiliza este archivo. Los objetos descritos en un archivo de configuración de Spring pueden instanciarse e inicializarse de dos maneras diferentes:

  • indicando, como se mostró anteriormente, los parámetros que se deben pasar al constructor del objeto
  • asignando valores a las propiedades del objeto (Property). En este caso, el objeto debe contar con un constructor por defecto que Spring utilizará para la instanciación.

Los objetos que, en una aplicación, tienen la función de prestar un servicio, a menudo se crean en una sola instancia. Se les llama singletons. Así, en nuestro ejemplo de aplicación de múltiples capas presentado al inicio de este documento, el acceso a la base de datos de artículos estará a cargo de una única instancia de la clase [ArticlesDaoPlainODBC]. 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 [ArticlesDaoPlainODBC] en un paquete llamado [istia.st.articles.dao]. Los tres datos necesarios para el constructor de este objeto se definen dentro de una etiqueta <object>...</object>. Habrá tantas etiquetas <object> como singletons se deban construir.

Detallamos la configuración:

<objects>
...
</objects>

<objects> es la etiqueta raíz de un archivo de configuración de Spring. Indica la descripción de los objetos singleton que se deben instanciar.

    <description>Gestion d'une table d'articles</description>

La etiqueta <description> es opcional. Se puede utilizar, por ejemplo, para describir la función del archivo de configuración.

<object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, articlesdao">
...    
</object>

La etiqueta <object> sirve para describir un objeto que se va a instanciar. Aquí tiene dos atributos:

  • name: identificador del objeto. Es a través de este nombre que el código externo hará referencia al objeto.
  • class: tiene el formato «nombre de clase, nombre de ensamblaje». La primera información es el nombre completo de la clase que se va a instanciar. La segunda, el nombre del DLL que contiene dicha clase. En nuestro ejemplo, la clase se encuentra en un archivo llamado [articlesdao.dll]

El contenido de la etiqueta <object> sirve para describir el modo de instanciación del objeto:

    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, articlesdao">
      <constructor-arg index="0">
            <value>odbc-firebird-articles</value>
        </constructor-arg>
         <constructor-arg index="1">
            <value>SYSDBA</value>
        </constructor-arg>
         <constructor-arg index="2">
            <value>masterkey</value>
        </constructor-arg>
    </object>

Recordemos la firma del fabricante de la clase [ArticlesDaoPlainODBC]:

        Public Sub New(ByVal DSN As String, ByVal user As String, ByVal passwd As String)

El objeto Spring [articlesdao] será instanciado por el constructor anterior con los tres datos del archivo de configuración: [odbc-firebird-articles, SYSDBA, masterkey].

¿En qué momento se llevará a cabo la creación de los objetos definidos en el archivo de Spring? En toda aplicación hay un método que, sin duda, será el primero en ejecutarse. Por lo general, es en este donde se solicita la creación de los singletons. La inicialización de una aplicación puede recaer en el método main de esa misma aplicación, si es que cuenta con uno. Para una aplicación ASP.NET, podría ser el método [Application_Start] del archivo [global.asax]. Para nuestra clase de prueba [Nunit], la inicialización de la aplicación se lleva a cabo en el método asociado al atributo <Setup()>.

¿Cómo utilizar el archivo de configuración anterior en nuestra clase [Nunit]? A continuación, un ejemplo:

Imports System
Imports System.Collections
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports ArticlesDaoSqlmap = istia.st.articles.dao.ArticlesDaoSqlMap
Imports Article = istia.st.articles.domain.Article
Imports System.Threading
Imports Spring.Objects.Factory.Xml
Imports System.IO

    <TestFixture()> _
    Public Class NunitSpringTestArticlesDaoPlainOdbc

         ' el objeto a probar
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
       ' se obtiene una instancia del generador de objetos de Spring
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config-plainodbc.xml", FileMode.Open))
       ' se solicita la instanciación del objeto DAO de artículos
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
        End Sub

        <Test()> _
        Public Sub testGetAllArticles()
             ' verificación visual
            listArticles()
        End Sub

         ' captura de pantalla
        Private Sub listArticles()
            Dim articles As IList = articlesDao.getAllArticles
            For i As Integer = 0 To articles.Count - 1
                Console.WriteLine(CType(articles(i), Article).ToString)
            Next
        End Sub
    End Class

Comentarios:

  • Para instanciar los objetos del archivo de configuración de Spring, se utiliza un objeto de tipo [XmlObjectFactory]. Se trata de un objeto de tipo «Factory», c.a.d, es decir, un objeto que sirve para crear otros objetos (Factory = fábrica). Spring cuenta con varios tipos de «Factory» según el archivo de configuración que se utilice. En este caso, se trata de un archivo XML, por lo que se utiliza un tipo [XmlObjectFactory].
  • De manera bastante lógica, un objeto de tipo [XmlObjectFactory] necesita el nombre del archivo de configuración XML, en este caso [spring-config-plainodbc.xml]. Más precisamente, el tipo [XmlObjectFactory] se instancia con un flujo de lectura creado a partir del archivo XML, cuyo nombre se especifica.
  • Una vez creado el objeto de tipo [XmlObjectFactory], se obtiene un objeto del archivo de configuración mediante [XmlObjectFactory].getObject("identificador"), donde "identificador" es el atributo [id] de uno de los objetos del archivo de configuración.
  • Si el objeto solicitado aún no ha sido instanciado, Spring lo instancia utilizando la información de su archivo de configuración y devuelve una referencia al programa que lo llama. Si el objeto ya ha sido instanciado, Spring simplemente devuelve la referencia del objeto ya existente. Este es el principio del singleton.
  • Cabe señalar que la clase de prueba [Nunit] no conoce el nombre de la clase de acceso a datos. Este nombre se encuentra en el archivo de configuración. La clase de prueba simplemente solicita un objeto que implemente la interfaz [IArticlesDao]:
         ' el objeto a probar
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
...
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
        End Sub

Ahí radica todo el interés de Spring. Si cambiamos la clase de implementación, no será necesario modificar nuestra clase de prueba. Simplemente modificaremos el archivo de configuración de Spring. La clase de prueba, por su parte, se limita a trabajar con una interfaz y no con una clase.

Terminemos esta presentación con algunos puntos prácticos.

Cuando escribimos «Spring va a instanciar...», ¿qué queremos decir exactamente? Para la plataforma .NET, Spring se encuentra en tres archivos:

Image

Para un proyecto .NET creado con Visual Studio y que deba utilizar Spring, se procederá de la siguiente manera:

  • los tres archivos mencionados anteriormente se colocarán en la carpeta [bin] del proyecto
  • [Spring.Core.dll] debe formar parte de las referencias del proyecto:

Image

  • las clases que utilizan Spring deberán importar ciertos espacios de nombres, entre los que suele encontrarse el siguiente:
Imports Spring.Objects.Factory.Xml

Otro punto práctico: ¿dónde se coloca el archivo de configuración de Spring? Hay varias ubicaciones posibles. Una de ellas es la carpeta [bin] del proyecto. Ahí es donde se colocó el archivo [spring-config-plainodbc.xml] del ejemplo analizado.