Skip to content

7. Uma solução

7.1. O projeto do Visual Studio

Comentários:

  • Os ficheiros necessários para o Spring foram colocados na pasta [bin]: Spring.Core.dll, log4net.dll, Spring.Core.xml
  • Os ficheiros de configuração do Spring [spring-config-3tier-*.xml] também foram colocados na pasta [bin]
  • No diretório raiz [istia], encontrará as várias classes da aplicação

O projeto foi configurado para gerar a DLL [spring3tier.dll] na pasta [bin]:

Image

7.2. O pacote [istia.st.spring3tier.dao]

A interface IDao:

Namespace istia.st.spring3tier.dao
    Public Interface IDao
        ' do something in the [dao] layer
        Function doSomethingInDaoLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

Uma primeira classe de implementação:

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

        ' do something in the diaper [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
  • A classe não tem campos privados
  • O método [doSomethingInDaoLayer] devolve a soma dos seus parâmetros, conforme solicitado.

Uma segunda classe de implementação:

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

        ' do something in the diaper [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
  • A classe não tem campos privados
  • O método [doSomethingInDaoLayer] devolve a diferença entre os seus parâmetros, conforme solicitado.

7.3. O pacote [istia.st.spring3tier.domain]

A interface IDomain:

Namespace istia.st.spring3tier.domain
    Public Interface IDomain
        ' do something in the [domain] layer
        Function doSomethingInDomainLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

Uma primeira classe de implementação IDomainImpl1:

Imports istia.st.spring3tier.dao

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

        ' private fields
        Private _dao As IDao

        ' associated property
        Public WriteOnly Property dao() As IDao
            Set(ByVal Value As IDao)
                _dao = Value
            End Set
        End Property

        ' default builder
        Public Sub New()
        End Sub

        ' do something in the [domain] layer
        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
  • A classe possui um campo privado que é uma referência ao singleton do tipo [IDao], o qual fornece acesso à camada [Dao]. Este campo será inicializado pelo Spring (injeção de dependências) quando o objeto for construído.
  • O método [doSomethingInDomainLayer] incrementa os seus parâmetros e, em seguida, passa-os para o método [doSomethingInDaoLayer] do singleton [dao]

Uma segunda classe de implementação, IDomainImpl2:

Imports istia.st.spring3tier.dao

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

        ' private fields
        Private _dao As IDao

        ' associated property
        Public WriteOnly Property dao() As IDao
            Set(ByVal Value As IDao)
                _dao = Value
            End Set
        End Property

        ' default builder
        Public Sub New()
        End Sub

        ' do something in the [domain] layer
        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
  • A classe possui um campo privado que é uma referência ao singleton [IDao], o qual fornece acesso à camada [Dao]. Este campo será inicializado pelo Spring (injeção de dependências) quando o objeto for construído.
  • O método [doSomethingInDomainLayer] decrementa os seus parâmetros e, em seguida, passa-os para o método [doSomethingInDaoLayer] do singleton [dao]

7.4. O pacote [istia.st.spring3tier.control]

A interface IControl:

Namespace istia.st.spring3tier.control
    Public Interface IControl
        ' do something in the [control] layer
        Function doSomethingInControlLayer(ByVal a As Integer, ByVal b As Integer) As Integer
    End Interface
End Namespace

Uma primeira classe de implementação ControlImpl1:

Imports istia.st.spring3tier.domain

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

        ' private fields
        Private _domain As IDomain

        ' associated property
        Public WriteOnly Property domain() As IDomain
            Set(ByVal Value As IDomain)
                _domain = Value
            End Set
        End Property

        ' default builder
        Public Sub New()
        End Sub

        ' do something in the [control] layer
        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
  • A classe possui um campo privado que é uma referência ao singleton do tipo [IDomain], o qual fornece acesso à camada [Domain]. Este campo será inicializado pelo Spring (injeção de dependências) quando o objeto for construído.
  • O método [doSomethingInControlLayer] incrementa os seus parâmetros e, em seguida, passa-os para o método [doSomethingInDomainLayer] do singleton [domain]

Uma segunda classe de implementação, IControlImpl2:

Imports istia.st.spring3tier.domain

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

        ' private fields
        Private _domain As IDomain

        ' associated property
        Public WriteOnly Property domain() As IDomain
            Set(ByVal Value As IDomain)
                _domain = Value
            End Set
        End Property

        ' default builder
        Public Sub New()
        End Sub

        ' do something in the [control] layer
        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
  • A classe possui um campo privado que é uma referência ao singleton do tipo [IDomain], o qual fornece acesso à camada [Domain]. Este campo será inicializado pelo Spring (injeção de dependências) quando o objeto for construído.
  • O método [doSomethingInControlLayer] decrementa os seus parâmetros e, em seguida, passa-os para o método [doSomethingInDomainLayer] do singleton [domain].

7.5. Os ficheiros de configuração [Spring]

O ficheiro [spring-config-3tier-1.xml] utiliza a versão 1 das implementações:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
     <!-- the dao class -->
    <object id="dao" type="istia.st.spring3tier.dao.DaoImpl1, spring3tier"></object>
     <!-- the domain class -->
    <object id="domain" type="istia.st.spring3tier.domain.DomainImpl1, spring3tier">
        <property name="dao">
            <ref object="dao" />
        </property>
    </object>
     <!-- the control class -->
    <object id="control" type="istia.st.spring3tier.control.ControlImpl1, spring3tier">
        <property name="domain">
            <ref object="domain" />
        </property>
    </object>
</objects>

O ficheiro [spring-config-3tier-2.xml] utiliza a versão 2 das implementações:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
     <!-- the dao class -->
    <object id="dao" type="istia.st.spring3tier.dao.DaoImpl2, spring3tier"></object>
     <!-- the domain class -->
    <object id="domain" type="istia.st.spring3tier.domain.DomainImpl2, spring3tier">
        <property name="dao">
            <ref object="dao" />
        </property>
    </object>
     <!-- the control class -->
    <object id="control" type="istia.st.spring3tier.control.ControlImpl2, spring3tier">
        <property name="domain">
            <ref object="domain" />
        </property>
    </object>
</objects>

7.6. O pacote de testes [istia.st.spring3tier.tests]

Um teste 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
        ' the singleton factories
        Private factory1 As XmlObjectFactory
        Private factory2 As XmlObjectFactory

        <SetUp()> _
        Public Sub init()
            ' singleton factories are created
            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()
            ' we destroy the singletons
            factory1.Dispose()
            factory2.Dispose()
            ' we free the singletons factories
            factory1 = Nothing
            factory2 = Nothing
        End Sub

        <Test()> _
        Public Sub test()
            'we retrieve an implementation of the IControl interface
            Dim control1 As IControl = CType(factory1.GetObject("control"), IControl)
            ' we use the
            Dim a1 As Integer = 10, b1 As Integer = 20
            Dim res1 As Integer = control1.doSomethingInControlLayer(a1, b1)
            Assert.AreEqual(34, res1)
            ' we retrieve another implementation of the IControl interface
            Dim control2 As IControl = CType(factory2.GetObject("control"), IControl)
            ' we use the
            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

A execução deste teste produz os seguintes resultados:

Image

Os leitores que estiverem a visualizar uma versão a cores deste documento verão que os resultados são «verdes», indicando que o teste foi bem-sucedido.

7.7. Outro tipo de ficheiro de configuração do Spring

Uma aplicação VB.NET pode ser configurada utilizando um ficheiro chamado [App.config]. Este ficheiro é colocado na raiz do projeto do Visual Studio:

Image

O Spring pode utilizar este ficheiro de configuração. Considere o seguinte ficheiro [App.config], inspirado em exemplos da documentação [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>
             <!-- an initial configuration -->
             <!-- the dao class -->
            <object id="dao1" type="istia.st.spring3tier.dao.DaoImpl1, spring3tier"></object>
             <!-- the domain class -->
            <object id="domain1" type="istia.st.spring3tier.domain.DomainImpl1, spring3tier">
                <property name="dao">
                    <ref object="dao1" />
                </property>
            </object>
             <!-- the control class -->
            <object id="control1" type="istia.st.spring3tier.control.ControlImpl1, spring3tier">
                <property name="domain">
                    <ref object="domain1" />
                </property>
            </object>
             <!-- a second configuration -->
             <!-- the dao class -->
            <object id="dao2" type="istia.st.spring3tier.dao.DaoImpl2, spring3tier"></object>
             <!-- the domain class -->
            <object id="domain2" type="istia.st.spring3tier.domain.DomainImpl2, spring3tier">
                <property name="dao">
                    <ref object="dao2" />
                </property>
            </object>
             <!-- the control class -->
            <object id="control2" type="istia.st.spring3tier.control.ControlImpl2, spring3tier">
                <property name="domain">
                    <ref object="domain2" />
                </property>
            </object>
        </objects>
    </spring>
</configuration>

Nota: As informações abaixo são fornecidas com reservas. Não tenho a certeza de ter compreendido corretamente o significado de todos os elementos no ficheiro de configuração acima.


A sintaxe XML do ficheiro [App.config] exige que este siga este formato:

<configuration>
....
</configuration>

A gestão das várias secções do [App.config] pode ser delegada a programas externos. É isso que se faz aqui na secção [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>

O código acima significa que a secção denominada [spring/context] deve ser gerida pela classe [Spring.Context.Support.ContextHandler] encontrada no assembly [Spring.Core.dll], e que a secção denominada [spring/objects] deve ser tratada pela classe [Spring.Context.Support.DefaultSectionHandler], que também se encontra no assembly [Spring.Core.dll].

A secção [spring/context] é a seguinte:

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

Isto parece indicar que a secção [spring/objects] do ficheiro de configuração deve ser gerida pela classe [Spring.Context.Support.XmlApplicationHandler], que se encontra no assembly [Spring.Core.dll]. Esta classe deve utilizar um recurso XML localizado em [config://spring/objects], ou seja, a secção [spring/objects] do ficheiro de configuração atual.

Na secção [spring/objects], encontramos a sintaxe Spring com a qual já estamos familiarizados.

Na secção <objects>... </objects> do ficheiro [App.config], definimos duas configurações possíveis para a nossa aplicação de 3 camadas:

  • uma que utiliza a versão 1 das implementações da interface
  • outra que utiliza a versão 2 dessas mesmas implementações

Agora, como se utiliza o ficheiro [App.config]?

O código a seguir mostra uma aplicação de consola que utiliza o ficheiro [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()
            ' the Spring context that will allow us to retrieve singletons
            Dim contexte As IApplicationContext = CType(ConfigurationSettings.GetConfig("spring/context"), IApplicationContext)
            ' we retrieve a 1st implementation of the IControl interface
            Dim control1 As IControl = CType(contexte.GetObject("control1"), IControl)
            ' we use the
            Dim a1 As Integer = 10, b1 As Integer = 20
            Console.WriteLine("res1({0},{1})={2}", a1, b1, control1.doSomethingInControlLayer(a1, b1))
            ' we retrieve another implementation of the IControl interface
            Dim control2 As IControl = CType(contexte.GetObject("control2"), IControl)
            ' we use the
            Dim a2 As Integer = 10, b2 As Integer = 20
            Console.WriteLine("res2({0},{1})={2}", a2, b2, control2.doSomethingInControlLayer(a2, b2))
            ' break
            Console.WriteLine("Tapez [entrée] pour continuer...")
            Console.ReadLine()
        End Sub

    End Module
End Namespace

Comentários:

  • Nos exemplos do NUnit que utilizámos até agora, utilizámos um objeto [XmlObjectFactory] para obter os singletons de que precisávamos. Aqui, o objeto utilizado é do tipo [IApplicationContext], uma interface Spring. É obtido a partir do [App.config] utilizando a classe [ConfigurationSettings], uma classe tradicionalmente utilizada no .NET para processar ficheiros de configuração.
  • Solicitamos o manipulador para a secção [spring/context]. Se consultarmos o ficheiro [App.config], vemos que este manipulador é do tipo [XmlApplicationContext] e é responsável por gerir a secção [spring/objects] do [App.config].
  • Assim que o objeto [IApplicationContext] é recuperado, é utilizado tal como o objeto [XmlObjectFactory] que temos vindo a utilizar até agora.

O programa anterior chama-se [MainTestSpring3tier.vb] e está localizado no pacote [tests]:

Image

O projeto [spring3tier] está configurado de forma a que [MainTestSpring3tier]:

Image

A execução do projeto produz os seguintes resultados:

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

7.8. Conclusão

O framework Spring oferece verdadeira flexibilidade tanto na arquitetura da aplicação como na configuração. Utilizámos o conceito de IoC, um dos dois pilares do Spring. O outro pilar é o AOP (Programação Orientada a Aspectos), que não abordámos. Permite adicionar «comportamento» a um método de classe através da configuração, sem modificar o código do método. Em termos simples, o AOP permite filtrar chamadas a determinados métodos:

  • o filtro pode ser executado antes ou depois do método alvo M, ou ambos.
  • O método M não tem conhecimento destes filtros. Estes são definidos no ficheiro de configuração do Spring.
  • O código do método M não é modificado. Os filtros são classes Java que devem ser implementadas. O Spring fornece filtros predefinidos, particularmente para a gestão de transações DBMS.
  • Os filtros são beans e, como tal, são definidos no ficheiro de configuração do Spring como beans.

Um filtro comum é o filtro de transação. Considere um método M da camada de negócios que realiza duas operações inseparáveis nos dados (uma unidade de trabalho). Ele chama dois métodos da camada DAO, M1 e M2, para realizar essas duas operações.

Por estar na camada de negócios, o método M abstrai o armazenamento de dados subjacente. Não precisa, por exemplo, de assumir que os dados estão num SGBD e que precisa de colocar as duas chamadas aos métodos M1 e M2 dentro de uma transação do SGBD. Cabe à camada DAO lidar com estes detalhes. Uma solução para o problema anterior é criar um método na camada DAO que, por sua vez, chame os métodos M1 e M2, envolvendo essas chamadas numa transação do SGBD.

A solução de filtragem AOP é mais flexível. Permite-lhe definir um filtro que, antes de chamar M, iniciará uma transação e, após a chamada, executará um commit ou um rollback, conforme apropriado.

Esta abordagem apresenta várias vantagens:

  • uma vez definido o filtro, este pode ser aplicado a vários métodos, por exemplo, todos aqueles que requerem uma transação
  • os métodos filtrados não precisam de ser reescritos
  • uma vez que os filtros a utilizar são definidos por configuração, podem ser alterados

Para mais informações: http://www.springframework.net.