Skip to content

7. Una solución

7.1. El proyecto de Visual Studio

Comentarios:

  • los archivos necesarios para Spring se han colocado en la carpeta [bin]: Spring.Core.dll, log4net.dll, Spring.Core.xml
  • Los archivos de configuración de Spring [spring-config-3tier-*.xml] también se han colocado en la carpeta [bin]
  • en la raíz [istia] se encuentran las diferentes clases de la aplicación

El proyecto se ha configurado para generar DLL [spring3tier.dll] en la carpeta [bin]:

Image

7.2. El paquete [istia.st.spring3tier.dao]

La interfaz IDao:

Namespace istia.st.spring3tier.dao
    Public Interface IDao
         ' hacer algo en la capa [dao]
        Function doSomethingInDaoLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

Una primera clase de implementación:

Namespace istia.st.spring3tier.dao
    Public Class DaoImpl1
        Implements istia.st.spring3tier.dao.IDao

         ' hacer algo en la capa [dao]
        Public Function doSomethingInDaoLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IDao.doSomethingInDaoLayer

            Return a + b
        End Function
    End Class

End Namespace
  • La clase no tiene ningún campo privado
  • El método [doSomethingInDaoLayer] devuelve la suma de sus parámetros tal y como se ha solicitado.

Una segunda clase de implementación:

Namespace istia.st.spring3tier.dao
    Public Class DaoImpl2
        Implements istia.st.spring3tier.dao.IDao

         ' hacer algo en la capa [dao]
        Public Function doSomethingInDaoLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IDao.doSomethingInDaoLayer

            Return a - b
        End Function
    End Class

End Namespace
  • La clase no tiene ningún campo privado
  • El método [doSomethingInDaoLayer] devuelve la diferencia entre sus parámetros tal y como se ha solicitado.

7.3. El paquete [istia.st.spring3tier.domain]

La interfaz IDomain:

Namespace istia.st.spring3tier.domain
    Public Interface IDomain
         ' hacer algo en la capa [domain]
        Function doSomethingInDomainLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

Una primera clase de implementación IDomainImpl1:

Imports istia.st.spring3tier.dao

Namespace istia.st.spring3tier.domain
    Public Class DomainImpl1
        Implements istia.st.spring3tier.domain.IDomain

         ' campos privados
        Private _dao As IDao

         ' propiedad asociada
        Public WriteOnly Property dao() As IDao
            Set(ByVal Value As IDao)
                _dao = Value
            End Set
        End Property

         ' constructor por defecto
        Public Sub New()
        End Sub

         ' hacer algo en la capa [domain]
        Public Function doSomethingInDomainLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IDomain.doSomethingInDomainLayer
            a += 1
            b += 1
            Return _dao.doSomethingInDaoLayer(a, b)
        End Function
    End Class

End Namespace
  • La clase tiene un campo privado que es una referencia al singleton de tipo [IDao], que da acceso a la capa [Dao]. Este campo será inicializado por Spring (inyección de dependencias) en el momento de la construcción del objeto.
  • El método [doSomethingInDomainLayer] incrementa sus parámetros y luego los pasa al método [doSomethingInDaoLayer] del singleton [dao]

Una segunda clase de implementación IDomainImpl2:

Imports istia.st.spring3tier.dao

Namespace istia.st.spring3tier.domain
    Public Class DomainImpl2
        Implements istia.st.spring3tier.domain.IDomain

         ' campos privados
        Private _dao As IDao

         ' propiedad asociada
        Public WriteOnly Property dao() As IDao
            Set(ByVal Value As IDao)
                _dao = Value
            End Set
        End Property

         ' constructor por defecto
        Public Sub New()
        End Sub

         ' hacer algo en la capa [domain]
        Public Function doSomethingInDomainLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IDomain.doSomethingInDomainLayer
            a -= 1
            b -= 1
            Return _dao.doSomethingInDaoLayer(a, b)
        End Function
    End Class

End Namespace
  • La clase tiene un campo privado que es una referencia al singleton de tipo [IDao], que da acceso a la capa [Dao]. Este campo será inicializado por Spring (inyección de dependencias) en el momento de la construcción del objeto.
  • El método [doSomethingInDomainLayer] decrementa sus parámetros y luego los pasa al método [doSomethingInDaoLayer] del singleton [dao]

7.4. El paquete [istia.st.spring3tier.control]

La interfaz IControl:

Namespace istia.st.spring3tier.control
    Public Interface IControl
         ' hacer algo en la capa [control]
        Function doSomethingInControlLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

Una primera clase de implementación ControlImpl1:

Imports istia.st.spring3tier.domain

Namespace istia.st.spring3tier.control
    Public Class ControlImpl1
        Implements istia.st.spring3tier.control.IControl

         ' campos privados
        Private _domain As IDomain

         ' propiedad asociada
        Public WriteOnly Property domain() As IDomain
            Set(ByVal Value As IDomain)
                _domain = Value
            End Set
        End Property

         ' constructor por defecto
        Public Sub New()
        End Sub

         ' hacer algo en la capa [control]
        Public Function doSomethingInControlLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IControl.doSomethingInControlLayer
            a += 1
            b += 1
            Return _domain.doSomethingInDomainLayer(a, b)
        End Function
    End Class

End Namespace
  • La clase tiene un campo privado que es una referencia al singleton de tipo [IDomain], que da acceso a la capa [Domain]. Este campo será inicializado por Spring (inyección de dependencias) en el momento de la construcción del objeto.
  • El método [doSomethingInControlLayer] incrementa sus parámetros y luego los pasa al método [doSomethingInDomainLayer] del singleton [domain]

Una segunda clase de implementación IControlImpl2:

Imports istia.st.spring3tier.domain

Namespace istia.st.spring3tier.control
    Public Class ControlImpl2
        Implements istia.st.spring3tier.control.IControl

         ' campos privados
        Private _domain As IDomain

         ' propiedad asociada
        Public WriteOnly Property domain() As IDomain
            Set(ByVal Value As IDomain)
                _domain = Value
            End Set
        End Property

         ' constructor por defecto
        Public Sub New()
        End Sub

         ' hacer algo en la capa [control]
        Public Function doSomethingInControlLayer(ByVal a As Integer, ByVal b As Integer) As Integer Implements IControl.doSomethingInControlLayer
            a -= 1
            b -= 1
            Return _domain.doSomethingInDomainLayer(a, b)
        End Function
    End Class

End Namespace
  • La clase tiene un campo privado que es una referencia al singleton de tipo [IDomain], que da acceso a la capa [Domain]. Este campo será inicializado por Spring (inyección de dependencias) en el momento de la construcción del objeto.
  • El método [doSomethingInControlLayer] decrementa sus parámetros y luego los pasa al método [doSomethingInDomainLayer] del singleton [domain].

7.5. Los archivos de configuración [Spring]

El archivo [spring-config-3tier-1.xml] utiliza la versión 1 de las implementaciones:

<?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 dao -->
    <object id="dao" type="istia.st.spring3tier.dao.DaoImpl1, spring3tier"></object>
     <!-- la clase domain -->
    <object id="domain" type="istia.st.spring3tier.domain.DomainImpl1, spring3tier">
        <property name="dao">
            <ref object="dao" />
        </property>
    </object>
     <!-- la clase control -->
    <object id="control" type="istia.st.spring3tier.control.ControlImpl1, spring3tier">
        <property name="domain">
            <ref object="domain" />
        </property>
    </object>
</objects>

El archivo [spring-config-3tier-2.xml] utiliza las versiones 2 de las implementaciones:

<?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 dao -->
    <object id="dao" type="istia.st.spring3tier.dao.DaoImpl2, spring3tier"></object>
     <!-- la clase domain -->
    <object id="domain" type="istia.st.spring3tier.domain.DomainImpl2, spring3tier">
        <property name="dao">
            <ref object="dao" />
        </property>
    </object>
     <!-- la clase control -->
    <object id="control" type="istia.st.spring3tier.control.ControlImpl2, spring3tier">
        <property name="domain">
            <ref object="domain" />
        </property>
    </object>
</objects>

7.6. El paquete de pruebas [istia.st.spring3tier.tests]

Una prueba Nunit [NunitTestSpring3tier.vb]:

Imports System
Imports Spring.Objects.Factory.Xml
Imports System.IO
Imports NUnit.Framework
Imports istia.st.spring3tier.control
Imports istia.st.spring3tier.domain

Namespace istia.st.springioc.tests

    <TestFixture()> _
    Public Class NunitTestSpring3tier
         ' las fábricas de singleton
        Private factory1 As XmlObjectFactory
        Private factory2 As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
             ' se crean las fábricas de singleton
            factory1 = New XmlObjectFactory(New FileStream("spring-config-3tier-1.xml", FileMode.Open))
            factory2 = New XmlObjectFactory(New FileStream("spring-config-3tier-2.xml", FileMode.Open))
        End Sub

        <TearDown()> _
        Public Sub destroy()
             ' se destruyen los singletons
            factory1.Dispose()
            factory2.Dispose()
             ' se liberan las fábricas de singletons
            factory1 = Nothing
            factory2 = Nothing
        End Sub

        <Test()> _
        Public Sub test()
             'se recupera una implementación de la interfaz IControl
            Dim control1 As IControl = CType(factory1.GetObject("control"), IControl)
             ' se utiliza la clase
            Dim a1 As Integer = 10, b1 As Integer = 20
            Dim res1 As Integer = control1.doSomethingInControlLayer(a1, b1)
            Assert.AreEqual(34, res1)
             ' se obtiene otra implementación de la interfaz IControl
            Dim control2 As IControl = CType(factory2.GetObject("control"), IControl)
             ' se utiliza la clase
            Dim a2 As Integer = 10, b2 As Integer = 20
            Dim res2 As Integer = control2.doSomethingInControlLayer(a2, b2)
            Assert.AreEqual(-10, res2)
        End Sub

    End Class
End Namespace

La ejecución de esta prueba da los siguientes resultados:

Image

El lector que tenga un version con los colores de este documento verá que los resultados están en «verde», lo que indica que la prueba se ha superado.

7.7. Otro tipo de archivo de configuración de Spring

Una aplicación VB.net se puede configurar mediante un archivo llamado [App.config]. Este archivo se coloca en la raíz del proyecto de Visual Studio:

Image

Spring puede aprovechar este archivo de configuración. Consideremos el siguiente archivo [App.config], inspirado en ejemplos de la documentación de [Spring.net]:

<?xml version="1.0" encoding="iso-8859-1" ?>
<configuration>
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
        <objects>
             <!-- una primera configuración -->
             <!-- la clase dao -->
            <object id="dao1" type="istia.st.spring3tier.dao.DaoImpl1, spring3tier"></object>
             <!-- la clase domain -->
            <object id="domain1" type="istia.st.spring3tier.domain.DomainImpl1, spring3tier">
                <property name="dao">
                    <ref object="dao1" />
                </property>
            </object>
             <!-- la clase control -->
            <object id="control1" type="istia.st.spring3tier.control.ControlImpl1, spring3tier">
                <property name="domain">
                    <ref object="domain1" />
                </property>
            </object>
             <!-- una segunda configuración -->
             <!-- la clase dao -->
            <object id="dao2" type="istia.st.spring3tier.dao.DaoImpl2, spring3tier"></object>
             <!-- la clase domain -->
            <object id="domain2" type="istia.st.spring3tier.domain.DomainImpl2, spring3tier">
                <property name="dao">
                    <ref object="dao2" />
                </property>
            </object>
             <!-- la clase de control -->
            <object id="control2" type="istia.st.spring3tier.control.ControlImpl2, spring3tier">
                <property name="domain">
                    <ref object="domain2" />
                </property>
            </object>
        </objects>
    </spring>
</configuration>

Nota: la información que figura a continuación se ofrece con reservas. No estoy seguro de haber entendido correctamente el significado de todos los elementos del archivo de configuración anterior.


La sintaxis XML del archivo [App.config] exige que respete la sintaxis:

<configuration>
....
</configuration>

La gestión de las diferentes secciones de [App.config] puede delegarse a programas externos. Esto es lo que se hace aquí en la sección [ConfigSections]:

    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>

El código anterior significa que la sección denominada [spring/context] debe ser gestionada por la clase [Spring.Context.Support.ContextHandler], quese encuentra en el conjunto [Spring.Core.dll], y que la sección denominada [spring/objects] debe ser gestionada por la clase [Spring.Context.Support.DefaultSectionHandler], que se encuentra también en el conjunto [Spring.Core.dll].

La sección [spring/context] es la siguiente:

    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
...
    </spring>

Parece indicar que la sección [spring/objects] del archivo de configuración debe ser gestionada por la clase [Spring.Context.Support.XmlApplicationHandler], que se encuentra en el ensamblado [Spring.Core.dll]. Esta clase debe utilizar un recurso XML cuya ubicación es [ config://spring/objects], c.a.d. la sección [spring/objects] del archivo de configuración actual.

En la sección [spring/objects] encontramos la sintaxis de Spring a la que ya estamos acostumbrados.

En la sección <objects>... </objects> del archivo [App.config] hemos definido dos configuraciones posibles para nuestra aplicación de tres capas:

  • una que utiliza la implementación de interfaces version 1
  • otra que utiliza la version 2 de esas mismas implementaciones

Ahora bien, ¿cómo se utiliza el archivo [App.config]?

El siguiente código muestra una aplicación de consola que utiliza el archivo [App.config] anterior:

Imports System
Imports Spring.Context
Imports System.IO
Imports NUnit.Framework
Imports istia.st.spring3tier.control
Imports istia.st.spring3tier.domain
Imports System.Configuration

Namespace istia.st.springioc.tests

    Module MainTestSpring3tier

        Public Sub main()
             ' el contexto Spring que nos permitirá recuperar los singletons
            Dim contexte As IApplicationContext = CType(ConfigurationSettings.GetConfig("spring/context"), IApplicationContext)
             ' recuperamos una primera implementación de la interfaz IControl
            Dim control1 As IControl = CType(contexte.GetObject("control1"), IControl)
             ' utilizamos la clase
            Dim a1 As Integer = 10, b1 As Integer = 20
            Console.WriteLine("res1({0},{1})={2}", a1, b1, control1.doSomethingInControlLayer(a1, b1))
             ' se recupera otra implementación de la interfaz IControl
            Dim control2 As IControl = CType(contexte.GetObject("control2"), IControl)
             ' se utiliza la clase
            Dim a2 As Integer = 10, b2 As Integer = 20
            Console.WriteLine("res2({0},{1})={2}", a2, b2, control2.doSomethingInControlLayer(a2, b2))
            ' pause
            Console.WriteLine("Tapez [entrée] pour continuer...")
            Console.ReadLine()
        End Sub

    End Module
End Namespace

Comentarios:

  • En los ejemplos NUnit que hemos utilizado hasta ahora, utilizábamos un objeto [XmlObjectFactory] para obtener los singletons que necesitábamos. Aquí, el objeto utilizado es de tipo [IApplicationContext], una interfaz de Spring. Se obtiene a partir de [App.config] gracias a la clase [ConfigurationSettings], clase utilizada tradicionalmente en .NET para explotar los archivos de configuración.
  • Se solicita el gestor de la sección [spring/context]. Si consultamos el archivo [App.config], descubrimos que este es de tipo [XmlApplicationContext] y que se encarga de gestionar la sección [spring/objects] de [App.config].
  • Una vez recuperado el objeto de tipo [IApplicationContext], se utiliza igual que el objeto [XmlObjectFactory] que habíamos utilizado hasta ahora.

El programa anterior se llama [MainTestSpring3tier.vb] y se encuentra en el paquete [tests]:

Image

El proyecto [spring3tier] está configurado para que [MainTestSpring3tier]:

Image

La ejecución del proyecto da los siguientes resultados:

res1(10,20)=34
res2(10,20)=-10
Tapez [entrée] pour continuer...

7.8. 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 añadir, 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 determinados métodos:

  • el filtro se puede ejecutar 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 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 habitual es el filtro transaccional. Tomemos un método M de la capa de negocio que realiza dos operaciones indisociables sobre los datos (unidad de trabajo). Este método invoca dos métodos M1 y M2 de la capa DAO para realizar estas dos operaciones.

Al encontrarse en la capa de negocio, el método M hace abstracción del soporte de estos datos. No tiene, por ejemplo, que los datos se encuentren en un SGBD y que tenga que 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 ocuparse de estos detalles. Una solución al problema anterior sería, por tanto, crear un método en la capa DAO que, a su vez, llamara a los métodos M1 y M2, llamadas que englobaría en una transacción de SGBD.

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

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 van a utilizar se definen mediante la configuración, por lo que se pueden cambiar

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