Skip to content

2. Configurar uma aplicação com o Spring

Consideremos uma aplicação clássica de três camadas:

Vamos supor que o acesso à camada DAO é controlado por uma interface [IArticlesDao]:

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

    Public Interface IArticlesDao
         ' lista de todos os artigos
        Function getAllArticles() As IList
         ' adiciona um artigo
        Function ajouteArticle(ByVal unArticle As Article) As Integer
         ' elimina um artigo
        Function supprimeArticle(ByVal idArticle As Integer) As Integer
         ' altera um artigo
        Function modifieArticle(ByVal unArticle As Article) As Integer
         ' procura um artigo
        Function getArticleById(ByVal idArticle As Integer) As Article
         ' elimina todos os artigos
        Sub clearAllArticles()
         ' insere artigos numa transação
        Sub doInsertionsInTransaction(ByVal articles As Article())
         ' altera o stock de um artigo
        Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
    End Interface
End Namespace

Na camada de acesso aos dados, ou camada DAO (Data Access Object), é frequente trabalhar com um SGBD. Consideremos o caso em que este é acedido através de um controlador ODBC. O esboço de uma classe que acede a esta fonte ODBC poderia ser o seguinte:

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-se o nome da fonte ODBC
            Me.DSN = DSN
             ' cria-se a cadeia de ligação
            Dim connectString As String = String.Format("DSN={0};UID={1};PWD={2}", DSN, user, passwd)
             'instanciar a ligação
            connexion = New OdbcConnection(connectString)
        End Sub

....
    End Class
End NameSpace

Para realizar uma operação na fonte ODBC, qualquer método necessita de um objeto [OdbcConnection] que represente a ligação à base de dados, através da qual transitarão as trocas de dados entre esta e a aplicação. Para criar este objeto, são necessárias três informações:

DSN As String
o nome da fonte ODBC
user As String
a identidade sob a qual se cria a ligação
passwd As String
a palavra-passe associada a essa identidade

A nossa classe [ArticlesDaoPlainODBC] obtém estas informações através do agente externo que instancia um membro da classe. Podemos questionar-nos sobre como é que este obtém as três informações necessárias para a instanciação da classe [ArticlesDaoPlainODBC]. Vejamos um exemplo. Suponhamos que se queira escrever uma classe de teste da camada [Dao]. Teríamos a seguinte arquitetura:

O esqueleto de uma classe de teste Nunit [http://www.nunit.org/] poderia ser o seguinte:

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

         ' o objeto a testar
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
             ' cria-se uma instância do objeto a testar
            articlesDao = New ArticlesDaoPlainODBC("odbc-firebird-articles", "SYSDBA", "masterkey")
        End Sub

        <Test()> _
        Public Sub testGetAllArticles()
             ' verificação visual
            listArticles()
        End Sub

         ' listagem do ecrã
        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

O ambiente de teste Nunit é uma adaptação para a plataforma .NET do ambiente JUnit que existe para a plataforma Java. Na classe acima, o método com o atributo <SetUp()> é executado antes de cada método de teste. O método com o atributo <TearDown()> é, por sua vez, executado após cada teste. Não há nenhum no exemplo acima. Aqui, vemos que o método [init], que possui o atributo <SetUp()>, instancia um objeto [ArticlesDaoPlainODBC], passando-lhe «em código» as três informações de que o construtor desse objeto necessita.

A nossa classe de teste fica à mercê de uma alteração numa das informações codificadas de forma «estática». Seria preferível que estas fossem registadas num ficheiro de configuração, de modo a evitar recompilações desnecessárias quando forem alteradas. A solução habitual para configurar uma aplicação é a utilização de um ficheiro onde se encontram todas as informações suscetíveis de sofrer alterações ao longo do tempo. Existe uma grande variedade de ficheiros de configuração. A tendência atual é utilizar ficheiros XML. Esta é a opção adotada pelo Spring. O ficheiro que configura um objeto [ArticlesDaoPlainODBC] poderia ser o seguinte:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">

<objects>
     <!-- a classe de implementação da interface 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>

O ficheiro de configuração do Spring descreve os objetos a instanciar. Em geral, não indica em que momento serão instanciados. O momento da sua instanciação é, portanto, decidido pelo código que utiliza esse ficheiro. Os objetos descritos num ficheiro de configuração do Spring podem ser instanciados e inicializados de duas formas diferentes:

  • indicando, como acima, os parâmetros a passar ao construtor do objeto
  • fornecendo valores às propriedades do objeto (Property). Neste caso, o objeto deve possuir um construtor por predefinição que o Spring utilizará para a instanciação.

Os objetos que, numa aplicação, têm como função prestar um serviço são frequentemente criados numa única instância. Chamam-se singletons. Assim, no nosso exemplo de aplicação multicamadas apresentado no início deste documento, o acesso à base de dados de artigos será assegurado por uma única instância da classe [ArticlesDaoPlainODBC]. Numa aplicação web, estes objetos de serviço atendem a vários clientes ao mesmo tempo. Não se cria um objeto de serviço por cliente.

O ficheiro de configuração do Spring acima permite criar um único objeto de serviço do tipo [ArticlesDaoPlainODBC] num pacote denominado [istia.st.articles.dao]. As três informações necessárias ao construtor deste objeto estão definidas dentro de uma baliza <object>...</object>. Haverá tantas balizas <object> quantos os singletons a construir.

Vamos detalhar a configuração:

<objects>
...
</objects>

<objects> é a baliza raiz de um ficheiro de configuração do Spring. Indica a descrição dos objetos singleton a instanciar.

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

A baliza <description> é opcional. Pode ser utilizada, por exemplo, para descrever a função do ficheiro de configuração.

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

A baliza <object> serve para descrever um objeto a ser instanciado. Aqui, possui dois atributos:

  • name: identificador do objeto. É através deste nome que o código externo fará referência ao objeto.
  • class: na forma «nome da classe, nome do conjunto». A primeira informação é o nome completo da classe a instanciar. A segunda é o nome do DLL que contém essa classe. No nosso exemplo, a classe encontra-se num ficheiro denominado [articlesdao.dll]

O conteúdo da baliza <object> serve para descrever o modo de instanciação do 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>

Recorde-se a assinatura do fabricante da classe [ArticlesDaoPlainODBC]:

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

O objeto Spring [articlesdao] será instanciado pelo construtor acima com as três informações do ficheiro de configuração: [odbc-firebird-articles, SYSDBA, masterkey].

Em que momento ocorrerá a criação dos objetos definidos no ficheiro Spring? Em qualquer aplicação, existe um método que é garantidamente o primeiro a ser executado. É geralmente nesse método que a criação dos singletons é solicitada. A inicialização de uma aplicação pode ser atribuída ao método main dessa mesma aplicação, caso esta o possua. Para uma aplicação ASP.NET, pode ser o método [Application_Start] do ficheiro [global.asax]. Para a nossa classe de teste [Nunit], a inicialização da aplicação ocorre no método associado ao atributo <Setup()>.

Como utilizar o ficheiro de configuração acima na nossa classe [Nunit]? Eis um exemplo:

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

         ' o objeto a testar
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
       ' obtém-se uma instância do criador de objetos Spring
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config-plainodbc.xml", FileMode.Open))
       ' solicita-se a instanciação do objeto DAO «artigos»
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
        End Sub

        <Test()> _
        Public Sub testGetAllArticles()
             ' verificação visual
            listArticles()
        End Sub

         ' listagem do ecrã
        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

Comentários:

  • para instanciar os objetos do ficheiro de configuração do Spring, utiliza-se um objeto do tipo [XmlObjectFactory]. Trata-se de um objeto do tipo «Factory», c.a.d, um objeto que serve para criar outros objetos (Factory = fábrica). O Spring dispõe de vários tipos de «Factory», dependendo do ficheiro de configuração utilizado. Neste caso, trata-se de um ficheiro XML e, por isso, utiliza-se um tipo [XmlObjectFactory].
  • De forma bastante lógica, um objeto do tipo [XmlObjectFactory] necessita do nome do ficheiro de configuração XML, neste caso, [spring-config-plainodbc.xml]. Mais precisamente, o tipo [XmlObjectFactory] é instanciado com um fluxo de leitura criado a partir do ficheiro XML, cujo nome é especificado.
  • Assim que o objeto do tipo [XmlObjectFactory] for criado, obtém-se um objeto do ficheiro de configuração através de [XmlObjectFactory].getObject("identificador"), em que "identificador" é o atributo [id] de um dos objetos do ficheiro de configuração.
  • Se o objeto solicitado ainda não tiver sido instanciado, o Spring instancia-o utilizando as informações do seu ficheiro de configuração e devolve uma referência ao programa chamador. Se o objeto já tiver sido instanciado, o Spring limita-se a devolver a referência do objeto já existente. Este é o princípio do singleton.
  • Note-se que a classe de teste [Nunit] não conhece o nome da classe de acesso aos dados. Esse nome encontra-se no ficheiro de configuração. A classe de teste limita-se a solicitar um objeto que implemente a interface [IArticlesDao]:
         ' o objeto a testar
        Private articlesDao As IArticlesDao

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

É aí que reside todo o interesse do Spring. Se alterarmos a classe de implementação, a nossa classe de teste não terá de ser alterada. Basta modificar o ficheiro de configuração do Spring. A classe de teste, por sua vez, limita-se a trabalhar com uma interface e não com uma classe.

Vamos terminar esta apresentação com alguns pontos práticos.

Quando escrevemos «O Spring vai instanciar...», o que queremos dizer exatamente? Para a plataforma .NET, o Spring está contido em três ficheiros:

Image

Para um projeto .NET criado com o Visual Studio e que deva utilizar o Spring, proceder-se-á da seguinte forma:

  • os três ficheiros acima referidos serão colocados na pasta [bin] do projeto
  • O ficheiro [Spring.Core.dll] deve fazer parte das referências do projeto:

Image

  • as classes que utilizam o Spring terão de importar determinados espaços de nomes, entre os quais, frequentemente, o seguinte:
Imports Spring.Objects.Factory.Xml

Outro aspeto prático: onde se coloca o ficheiro de configuração do Spring? Existem vários locais possíveis. Um deles é a pasta [bin] do projeto. Foi aí que foi colocado o ficheiro [spring-config-plainodbc.xml] do exemplo analisado.