Skip to content

2. Artigo 1 - Spring IoC

Objetivos deste documento:

  • explorar as possibilidades de configuração e integração do framework Spring (http://www.springframework.org)
  • definir e utilizar o conceito de IoC (Inversão de Controlo), também conhecido como Injeção de Dependências

2.1. Configurar uma aplicação de 3 camadas com o Spring

Considere uma aplicação clássica de 3 camadas:

Vamos assumir que o acesso às camadas de negócios e DAO é controlado por interfaces Java:

  1. a interface [IArticlesDao] para a camada de acesso aos dados
  1. a interface [IArticlesManager] para a camada de negócios

Na camada de acesso a dados, ou camada DAO (Data Access Object), é comum trabalhar com um SGBD e, portanto, com um controlador JDBC. Considere o esqueleto de uma classe que acede a uma tabela de artigos num SGBD:

public class ArticlesDaoPlainJdbc implements IArticlesDao {

     // connection to data source
    private String driverClassName=null;
    private Connection connexion=null;
    private String url = null;
    private String user = null;
    private String pwd = null;
 ....

    public List getAllArticles() {
        // the list of items is requested
        try {
             // load the JDBC driver
            Class.forName(driverClassName);
            // create a connection to BD
            connexion = DriverManager.getConnection(url, user, pwd);
            ...
        } catch (SQLException ex) {
            ...
        } finally {
            ...
        }
    }

Para realizar uma operação no SGBD, cada método requer um objeto [Connection] que representa a ligação à base de dados, através da qual os dados serão trocados entre a base de dados e o código Java. Para criar este objeto, são necessárias quatro informações:

String driverClassName
o nome da classe do controlador JDBC do SGBD
String url
o URL JDBC da base de dados a utilizar
String user
as credenciais utilizadas para estabelecer a ligação
String pwd
A palavra-passe para este nome de utilizador

Como é que a nossa classe [ArticlesDaoPlainJdbc] anterior pode obter esta informação? Existem várias possibilidades:

Solução 1 - a informação está codificada na classe:

1
2
3
4
5
6
7
8
public class ArticlesDaoPlainJdbc implements IArticlesDao {

     // connection to data source
    private final String driverClassName = "org.firebirdsql.jdbc.FBDriver";
    private String url = "jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb";
    private String user = "someone";
    private String pwd = "somepassword";
 ....

A desvantagem desta solução é que é necessário modificar o código Java sempre que alguma destas informações for alterada, como por exemplo, quando a palavra-passe for alterada.

Solução 2 — a informação é passada para o objeto durante a sua construção:

public class ArticlesDaoPlainJdbc implements IArticlesDao {

     // connection to data source
    private final String driverClassName;
    private String url;
    private String user;
    private String pwd;
 ....
    public ArticlesDaoPlainJdbc(String driverClassName,String url,String user,String pwd) {
      this.driverClassName=driverClassName;
    this.url=url;
    this.user=user;
    this.pwd=pwd;
    ...
    }

Aqui, o objeto recebe as informações necessárias para funcionar no momento em que é criado. O problema passa então para o código que lhe forneceu essas quatro informações. Como é que as obteve? A seguinte classe [ArticlesManagerWithDataBase], na camada de negócios, poderia criar um objeto [ArticlesDaoPlainJdbc] a partir da camada de acesso aos dados:

public class ArticlesManagerWithDataBase implements IArticlesManager {

    // a data access instance
    private IArticlesDao articlesDao;
 ....
    public ArticlesManagerWithDataBase (String driverClassName, String url, String user, String pwd, ...) {
        ... 
         // creation of a data access service
        articlesDao =(IArticlesDao)new ArticlesDaoPlainJdbc(driverClassName,url,user,pwd);
    ...
    }

    public ... doSomething(...){
        ...
    }
}

Podemos ver que, mais uma vez, a informação necessária para construir o objeto [ArticlesDaoPlainJdbc] é fornecida ao construtor do objeto [ArticlesManagerWithDataBase]. Podemos imaginar que esta informação lhe é passada por uma camada superior, como a camada da interface do utilizador. Chegamos assim gradualmente à camada mais alta da aplicação. Devido à sua posição, esta camada não é chamada por uma camada que lhe possa passar as informações de configuração de que necessita. Temos, portanto, de encontrar uma alternativa à configuração baseada no construtor. A abordagem padrão para configurar uma aplicação na sua camada mais elevada consiste em utilizar um ficheiro que contenha todas as informações suscetíveis de alteração ao longo do tempo. Poderá haver vários ficheiros deste tipo. Aquando do arranque da aplicação, uma camada de inicialização criará então a totalidade ou parte dos objetos necessários às várias camadas da aplicação.

Existe uma grande variedade de ficheiros de configuração. A tendência atual é utilizar ficheiros XML. Esta é a abordagem adotada pelo Spring. O ficheiro que configura um objeto [ArticlesDaoPlainJdbc] pode ter o seguinte aspeto:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- data access class -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
        <constructor-arg index="0">
            <value>org.firebirdsql.jdbc.FBDriver</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>someone</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>somepassword</value>
        </constructor-arg>
    </bean>
</beans>

Uma aplicação é um conjunto de objetos que o Spring denomina beans, porque seguem o padrão JavaBean para nomear acessadores e inicializadores (getters/setters) dos campos privados de um objeto. Os objetos numa aplicação que servem um propósito específico são frequentemente criados como uma única instância. Estes são chamados de singletons. Assim, no nosso exemplo de aplicação multicamadas aqui discutido, o acesso à base de dados de artigos será tratado por uma única instância da classe [ArticlesDaoPlainJdbc]. Para uma aplicação web, estes objetos de serviço atendem a vários clientes ao mesmo tempo. Não é criado um objeto de serviço para cada cliente.

O ficheiro de configuração Spring acima permite a criação de um único objeto de serviço do tipo [ArticlesDaoPlainJdbc] num pacote denominado [istia.st.articles.dao]. As quatro informações exigidas pelo construtor deste objeto são definidas dentro de uma tag <bean>...</bean>. Haverá tantas tags <bean> quantos os singletons a serem criados.

Quando é que os objetos definidos no ficheiro Spring serão criados? A inicialização da aplicação pode ser tratada pelo método main da aplicação, caso esta o possua. No caso de uma aplicação web, este pode ser o método [init] do servlet principal. Todas as aplicações possuem um método cuja execução é garantidamente a primeira a ocorrer. É geralmente dentro deste método que se realiza a criação dos singletons.

Vejamos um exemplo. Suponhamos que queremos testar a classe [ArticlesDaoPlainJdbc] anterior utilizando um teste JUnit. Uma classe de teste JUnit possui um método [setUp] que é executado antes de qualquer outro método. É aqui que iremos criar o singleton [ArticlesDaoPlainJdbc].

Se seguirmos a abordagem de passar informações de configuração através do construtor, teremos a seguinte classe de teste:

public class TestArticlesPlainJdbc extends TestCase {
    // tests the ArticlesDaoPlainJdbc item access class
     // the data source is defined in sprintest

     // an instance of the class under test
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception{
        // retrieves a data access instance
        articlesDao =
            (IArticlesDao) new ArticlesDaoPlainJdbc("org.firebirdsql.jdbc.FBDriver",
                "jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb","someone","somepassword");
    }

A classe de chamada [TestArticlesPlainJdbc] deve conhecer as quatro informações necessárias para inicializar o singleton [ArticlesDaoPlainJdbc] a ser construído.

Se seguirmos a abordagem de passar informações de configuração através de um ficheiro de configuração, poderíamos ter a seguinte classe de teste utilizando o ficheiro Spring descrito acima.

public class TestSpringArticlesPlainJdbc extends TestCase {
    // tests the ArticlesDaoJdbc item access class
     // the data source is defined in sprintest

     // an instance of the class under test
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception {
      // retrieves a data access instance
      articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
          "springArticlesPlainJdbc.xml"))).getBean("articlesDao");
    }

Aqui, a classe de chamada [TestSpringArticlesPlainJdbc] não precisa de saber as informações necessárias para inicializar o singleton a ser construído. Basta saber:

  1. [springArticlesPlainJdbc.xml]: o nome do ficheiro de configuração Spring descrito acima
  2. [articlesDao]: o nome do singleton a ser criado

Uma modificação no ficheiro de configuração, fora destas duas entidades, não tem impacto no código Java. Este método de configurar os objetos de uma aplicação é muito flexível. Para se configurar, a aplicação precisa de saber apenas duas coisas:

  • o nome do ficheiro Spring que contém as definições dos singletons a criar
  • os nomes desses singletons, que o código Java utiliza para obter uma referência aos objetos aos quais foram associados através do ficheiro de configuração

2.2. Injeção de Dependências e Inversão de Controlo

Vamos agora apresentar o conceito de Injeção de Dependências utilizado pelo Spring para configurar aplicações. O termo Inversão de Controlo (IoC) também é utilizado. Considere a construção do singleton [ArticlesManagerWithDataBase] na camada de negócios da nossa aplicação:

Para aceder aos dados do SGBD, a camada de negócio deve utilizar os serviços de um objeto que implemente a interface [IArticlesDao], por exemplo, um objeto do tipo [ArticlesDaoPlainJdbc]. O código para a classe [ArticlesManagerWithDataBase] pode ser semelhante ao seguinte:

public class ArticlesManagerWithDataBase implements IArticlesManager {

     // a data access instance
    private IArticlesDao articlesDao;
 ....
    public ArticlesManagerWithDataBase (String driverClassName, String url, String user, String pwd, ...) {
        ... 
         // creation of a data access service
        articlesDao =(IArticlesDao)new ArticlesDaoPlainJdbc(driverClassName,url,user,pwd);
    ...
    }

    public ... doSomething(...){
        ...
    }
}

A classe [ArticlesDaoPlainJdbc] deve implementar a interface [IArticlesDao] aqui:

public class ArticlesDaoPlainJdbc implements IArticlesDao {...}

Para criar o singleton [IArticlesDao] necessário para que a classe funcione, o seu construtor utiliza explicitamente o nome da classe que implementa a interface [IArticlesDao]:

articlesDao =(IArticlesDao) new ArticlesDaoPlainJdbc(...);

Temos, portanto, uma dependência codificada no nome da classe no código. Se a classe que implementa a interface [IArticlesDao] fosse alterada, o código no construtor anterior teria de ser modificado. Temos as seguintes relações entre os objetos:

A própria classe [ArticlesManagerWithDataBase] toma a iniciativa de criar o objeto [ArticlesDaoPlainJdbc] de que necessita. Voltando ao termo «inversão de controlo», podemos dizer que é ela que detém o «controlo» para criar o objeto de que necessita.

Se tivéssemos de escrever uma classe de teste JUnit para a classe [ArticlesManagerWithDataBase], poderia ficar mais ou menos assim:

public class TestArticlesManagerWithDataBase extends TestCase {
    // an instance of the business class under test
    private IArticlesManager articlesManager;

    protected void setUp() throws Exception {
        // creates an instance of the business class under test
        articlesManager =
            (IArticlesManager) new ArticlesManagerWithDataBase("org.firebirdsql.jdbc.FBDriver",
                "jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb","someone","somepassword");
    }

A classe de teste cria uma instância da classe de negócio [ArticlesManagerWithDataBase], que, por sua vez, cria uma instância da classe de acesso a dados [ArticlesDaoPlainJdbc] no seu construtor.

A solução Spring elimina a necessidade de a classe de negócio [ArticlesManagerWithDataBase] conhecer o nome [ArticlesDaoPlainJdbc] da classe de acesso a dados de que necessita. Isto permite que a classe seja alterada sem modificar o código Java da classe de negócio. O Spring permite a criação simultânea de ambos os singletons: um para a camada de acesso a dados e outro para a camada de negócio. O ficheiro de configuração do Spring irá definir um novo bean:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- data access class -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
        <constructor-arg index="0">
            <value>org.firebirdsql.jdbc.FBDriver</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>someone</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>somepassword</value>
        </constructor-arg>
    </bean>
    <bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
        <property name="articlesDao">
            <ref bean="articlesDao"/>
        </property>
    </bean>
</beans>

A nova funcionalidade é o bean que define o singleton da classe de negócio a ser criada:

    <bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
        <property name="articlesDao">
            <ref bean="articlesDao"/>
        </property>
    </bean>
  1. A classe que implementa o bean [articlesManager] é definida: [ArticlesManagerWithDataBase]
  2. Ao campo [articlesDao] do bean é atribuído um valor através da tag <property name="articlesDao">. Este é o campo definido na classe [ArticlesManagerWithDataBase]:
public class ArticlesManagerWithDataBase implements IArticlesManager {

  // data access interface
  private IArticlesDao articlesDao;

  public IArticlesDao getArticlesDao() {
    return articlesDao;
  }

  public void setArticlesDao(IArticlesDao articlesDao) {
    this.articlesDao = articlesDao;
  }

Para que o campo [articlesDao] seja inicializado pelo Spring e pela sua tag <property>, o campo deve seguir o padrão JavaBean e deve existir um método [setArticlesDao] para inicializar o campo [articlesDao]. Note que o nome do método deriva precisamente do nome do campo. Da mesma forma, existe frequentemente um método [get...] para recuperar o valor do campo. Aqui, trata-se do método [getArticlesDao]. Nesta nova versão, a classe [ArticlesManagerWithDataBase] já não possui um construtor. Já não necessita de um.

  • O valor que o Spring atribuirá ao campo [articlesDao] é o do bean [articlesDao] definido no seu ficheiro de configuração:
    <bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
        <property name="articlesDao">
            <ref bean="articlesDao"/>
        </property>
    </bean>
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
        <constructor-arg index="0">
    .............
    </bean>
  • Quando o Spring constrói o singleton [ArticlesManagerWithDataBase], também cria o singleton [ArticlesDaoPlainJdbc]:
    • O Spring estabelecerá um gráfico de dependências dos beans e verificará que o bean [articlesManager] depende do bean [articlesDao]
    • construirá o bean [articlesDao], ou seja, um objeto do tipo [ArticlesDaoPlainJdbc]
    • depois, irá construir o bean [articlesManager] do tipo [ArticlesManagerWithDataBase]

Agora, imaginemos um teste JUnit para a classe [ArticlesManagerWithDataBase]. Pode ficar assim:

public class TestSpringArticlesManagerWithDataBase extends TestCase {
    // test business class [ArticlesManagerWithDataBase]

    // an instance of the business class under test
    private IArticlesManager articlesManager;

    protected void setUp() throws Exception {
      // retrieves a data access instance
      articlesManager = (IArticlesManager) (new XmlBeanFactory(new ClassPathResource(
          "springArticlesManagerWithDataBase.xml"))).getBean("articlesManager");
    }

Vamos acompanhar o processo de criação dos dois singletons definidos no ficheiro Spring denominado [springArticlesManagerWithDataBase.xml].

  • O método [setUp] acima solicita uma referência ao bean denominado [articlesManager]
  • O Spring consulta o seu ficheiro de configuração e encontra o bean [articlesManager]. Se este já tiver sido criado, simplesmente devolve uma referência ao objeto (singleton); caso contrário, cria-o.
  • O Spring deteta a dependência do bean [articlesManager] em relação ao bean [articlesDao]. Por isso, cria o singleton [articlesDao] do tipo [ArticlesDaoPlainJdbc] se este ainda não tiver sido criado (como singleton).
  • Cria o singleton [articlesManager] do tipo [ArticlesManagerWithDataBase]

Este mecanismo pode ser representado graficamente da seguinte forma:

Vamos relembrar a estrutura da classe [ArticlesManagerWithDataBase]:

public class ArticlesManagerWithDataBase implements IArticlesManager {

  // data access interface
  private IArticlesDao articlesDao;

  public IArticlesDao getArticlesDao() {
    return articlesDao;
  }

  public void setArticlesDao(IArticlesDao articlesDao) {
    this.articlesDao = articlesDao;
  }

Assim que o Spring terminar de construir os singletons, temos um objeto do tipo [ArticlesManagerWithDataBase] cujo campo [articlesDao] é inicializado sem que ele saiba como. Dizemos que injetamos uma dependência no objeto [ArticlesManagerWithDataBase]. Dizemos também que invertemos o controlo: já não é o objeto [ArticlesManagerWithDataBase] que toma a iniciativa de criar o objeto que implementa a interface [IArticlesDao] de que necessita; em vez disso, é a aplicação de nível superior (quando se inicializa) que se encarrega de criar todos os objetos de que necessita, gerindo as suas interdependências.

A principal vantagem de configurar o singleton [ArticlesManagerWithDataBase] através de um ficheiro Spring é que agora podemos alterar a classe de implementação correspondente ao campo [articlesDao] da classe [ArticlesManagerWithDataBase] sem modificar o seu código. Basta alterar o nome da classe na definição do bean [articlesDao] no ficheiro Spring:

    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
...
    </bean>

passará a ser, por exemplo:

    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoIbatisSqlMap">
...
    </bean>

O bean [ArticlesManagerWithDataBase] funcionará com esta nova classe de acesso a dados sem sequer se aperceber.

2.3. Spring IoC na prática

2.3.1. Exemplo 1

Considere a seguinte classe:

package istia.st.springioc.domain;

public class Personne {
  private String nom;
  private int age;

   // person display
  public String toString() {
    return "nom=[" + this.nom + "], age=[" + this.age + "]";
  }

   // init-close
  public void init() {
    System.out.println("init personne [" + this.toString() + "]");
  }

  public void close() {
    System.out.println("destroy personne [" + this.toString() + "]");
  }

   // getters-setters
  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  public String getNom() {
    return nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }
}

A classe tem:

  • dois campos privados, nome e idade
  • métodos getter e setter para estes dois campos
  • um método toString para recuperar o valor do objeto [Person] como uma string
  • um método init que será chamado pelo Spring quando o objeto for criado, e um método close que será chamado quando o objeto for destruído

Para criar objetos do tipo [Person], utilizaremos o seguinte ficheiro Spring:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="personne1" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </bean>
    <bean id="personne2" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </bean>
</beans>

Este ficheiro será denominado config.xml.

  • Ele define dois beans com as respetivas chaves "person1" e "person2" do tipo [Person]
  • Inicializa os campos [name, age] para cada pessoa
  • Define os métodos a serem chamados durante a construção inicial do objeto [init-method] e durante a destruição do objeto [destroy-method]

Para os nossos testes, utilizaremos uma única classe de teste JUnit à qual iremos adicionar métodos sucessivamente. A primeira versão desta classe será a seguinte:

package istia.st.springioc.tests;

import istia.st.springioc.domain.Personne;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import junit.framework.TestCase;

public class Tests extends TestCase {

   // bean factory
  private ListableBeanFactory bf;

   // init tests
  public void setUp() {
    bf = new XmlBeanFactory(new ClassPathResource("config.xml"));
  }

  public void test1() {
    // retrieve [Person] bean keys from the Spring file
    Personne personne1 = (Personne) bf.getBean("personne1");
    System.out.println("personne1=" + personne1.toString());
    Personne personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());
    personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());
  }
}

Comentários:

  • Para recuperar os beans definidos no ficheiro [config.xml], utilizamos um objeto do tipo [ListableBeanFactory]. Existem outros tipos de objetos que permitem o acesso aos beans. O objeto [ListableBeanFactory] é obtido no método [setUp] da classe de teste e armazenado numa variável privada. Assim, estará disponível para todos os métodos de teste.
  • O ficheiro [config.xml] será colocado no [ClassPath] da aplicação, ou seja, num dos diretórios pesquisados pela Máquina Virtual Java quando procura uma classe referenciada pela aplicação. O objeto [ClassPathResource] é utilizado para pesquisar um recurso no [ClassPath] de uma aplicação, neste caso o ficheiro [config.xml].
  • O Spring pode utilizar ficheiros de configuração em vários formatos. O objeto [XmlBeanFactory] é utilizado para analisar um ficheiro de configuração no formato XML.
  • O processamento de um ficheiro Spring devolve um objeto do tipo [ListableBeanFactory], neste caso o objeto bf. Com este objeto, obtém-se um bean identificado pela chave C através de bf.getBean(C).
  • O método [test1] recupera e apresenta os valores dos beans com as chaves "person1" e "person2".

A estrutura do projeto Eclipse da nossa aplicação é a seguinte:

Image

Comentários:

  • A pasta [src] contém o código-fonte. O código compilado será colocado numa pasta [bin] que não é mostrada aqui.
  • O ficheiro [config.xml] está localizado na raiz da pasta [src]. A compilação do projeto copia-o automaticamente para a pasta [bin], que faz parte do [ClassPath] da aplicação. É aqui que ele é procurado pelo objeto [ClassPathResource].
  • A pasta [lib] contém três bibliotecas Java necessárias para a aplicação:
      • commons-logging.jar e spring-core.jar para as classes Spring
      • junit.jar para as classes JUnit
  • A pasta [lib] também faz parte do [ClassPath] da aplicação

A execução do método [test1] do teste JUnit produz os seguintes resultados:

18 sept. 2004 11:28:53 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
18 sept. 2004 11:28:53 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne1'
init personne [nom=[Simon], age=[40]]
personne1=nom=[Simon], age=[40]
18 sept. 2004 11:28:53 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne2'
init personne [nom=[Brigitte], age=[20]]
personne2=nom=[Brigitte], age=[20]
personne2=nom=[Brigitte], age=[20]

Comentários:

  • O Spring regista vários eventos utilizando a biblioteca [commons-logging.jar]. Estes registos ajudam-nos a compreender melhor como o Spring funciona.
  • O ficheiro [config.xml] foi carregado e, em seguida, processado
  • A operação*
Personne personne1 = (Personne) bf.getBean("personne1");

forçou a criação do bean [person1]. Podemos ver o registo do Spring relativo a isto. Como tínhamos escrito [init-method="init"] na definição do bean [person1], o método [init] do objeto [Person] criado foi executado. A mensagem correspondente é apresentada.

  • A operação
System.out.println("personne1=" + personne1.toString());

exibiu o valor do objeto [Person] criado.

  • O mesmo fenómeno ocorre com o bean-chave [person2].
  • A última operação
    personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());

não resultou na criação de um novo objeto do tipo [Person]. Se assim fosse, o método [init] teria sido exibido, o que não é o caso aqui. Este é o princípio do singleton. Por predefinição, o Spring cria apenas uma única instância dos beans no seu ficheiro de configuração. Trata-se de um serviço de referência a objetos. Se lhe for solicitada a referência a um objeto que ainda não tenha sido criado, ele cria-o e devolve uma referência. Se o objeto já tiver sido criado, o Spring devolve simplesmente uma referência ao mesmo.

  • Note-se que não há qualquer vestígio do método [close] do objeto [Person], apesar de termos escrito [destroy-method=close] na definição do bean. É possível que este método só seja executado quando a memória ocupada pelo objeto for recuperada pelo coletor de lixo. Quando isso acontece, a aplicação já terminou e escrever no ecrã não tem qualquer efeito. A verificar.

Agora que abordámos os conceitos básicos de uma configuração Spring, poderemos avançar nas nossas explicações um pouco mais rapidamente.

2.3.2. Exemplo 2

Considere a seguinte nova classe [Car]:

package istia.st.springioc.domain;

public class Voiture {
  private String marque;
  private String type;
  private Personne propriétaire;

   // manufacturers

  public Voiture() {
  }

  public Voiture(String marque, String type, Personne propriétaire) {
    this.marque = marque;
    this.type = type;
    this.propriétaire = propriétaire;
  }

   // toString
  public String toString() {
    return "Voiture : marque=[" + this.marque + "] type=[" + this.type
        + "] propriétaire=[" + this.propriétaire + "]";
  }

     // getters-setters
  public String getMarque() {
    return marque;
  }

  public void setMarque(String marque) {
    this.marque = marque;
  }

  public Personne getPropriétaire() {
    return propriétaire;
  }

  public void setPropriétaire(Personne propriétaire) {
    this.propriétaire = propriétaire;
  }

  public String getType() {
    return type;
  }

  public void setType(String type) {
    this.type = type;
  }

   // init-close
  public void init() {
    System.out.println("init voiture [" + this.toString() + "]");
  }

  public void close() {
    System.out.println("destroy voiture [" + this.toString() + "]");
  }

}

A classe tem:

  • três campos privados: type, make e owner. Estes campos podem ser inicializados e lidos utilizando os métodos públicos get e set. Também podem ser inicializados utilizando o construtor Car(String, String, Person). A classe possui ainda um construtor sem argumentos para cumprir a norma JavaBean.
  • um método toString para recuperar o valor do objeto [Car] como uma string
  • um método init que será chamado pelo Spring imediatamente após a criação do objeto, um método close que será chamado quando o objeto for destruído

Para criar objetos do tipo [Car], utilizaremos o seguinte ficheiro [config.xml] do Spring:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="personne1" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </bean>
    <bean id="personne2" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </bean>
    <bean id="voiture1" class="istia.st.springioc.domain.Voiture" 
        init-method="init" destroy-method="close">
        <constructor-arg index="0">
            <value>Peugeot</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>307</value>
        </constructor-arg>
        <constructor-arg index="2">
            <ref bean="personne2"></ref>
        </constructor-arg>
    </bean>
</beans>

Este ficheiro adiciona um bean com a chave "car1" do tipo [Car] às definições anteriores. Para inicializar este bean, poderíamos ter escrito:

    <bean id="voiture1" class="istia.st.springioc.domain.Voiture" 
        init-method="init" destroy-method="close">
        <property name="marque">
            <value>Peugeot</value>
        </property>
        <property name="type">
            <value>307</value>
        </property>
        <property name="propriétaire">
            <ref bean="personne2"/>
        </property>
    </bean>

Em vez de escolher o método já apresentado, optámos aqui por utilizar o construtor Car(String, String, Person) da classe. Além disso, o bean [car1] define o método a ser chamado durante a construção inicial do objeto [init-method] e o método a ser chamado durante a destruição do objeto [destroy-method].

Para os nossos testes, utilizaremos a classe de teste JUnit já apresentada, adicionando-lhe o seguinte método [test2]:

1
2
3
4
5
  public void test2() {
     // recovery of bean [voiture1]
    Voiture Voiture1 = (Voiture) bf.getBean("voiture1");
    System.out.println("Voiture1=" + Voiture1.toString());
  }

O método [test2] recupera o bean [car1] e apresenta-o.

A estrutura do projeto Eclipse permanece a mesma do teste anterior. A execução do método [test2] do teste JUnit produz os seguintes resultados:

18 sept. 2004 14:56:10 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
18 sept. 2004 14:56:10 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'voiture1'
18 sept. 2004 14:56:10 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne2'
init personne [nom=[Brigitte], age=[20]]
18 sept. 2004 14:56:10 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'voiture1' instantiated via constructor [public istia.st.springioc.domain.Voiture(java.lang.String,java.lang.String,istia.st.springioc.domain.Personne)]
init voiture [Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]]
Voiture1=Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]

Comentários:

  1. O método [test2] solicita uma referência ao bean [car1]
  2. Linha 4: O Spring começa a criar o bean [car1] porque este bean ainda não foi criado (singleton)
  3. linha 6: como o bean [car1] faz referência ao bean [person2], este último é, por sua vez, construído
  4. Linha 7: o bean [person2] foi criado. O seu método [init] é então executado.
  5. Linha 9: O Spring indica que utilizará um construtor para criar o bean [car1]
  6. linha 10: o bean [car1] foi criado. O seu método [init] é então executado.
  7. linha 11: o método [test2] exibe o valor do bean [car1]

2.3.3. Exemplo 3

Apresentamos a seguinte nova classe [PersonGroup]:

package istia.st.springioc.domain;

import java.util.Map;

public class GroupePersonnes {
  private Personne[] membres;
  private Map groupesDeTravail;

   // getters - setters
  public Personne[] getMembres() {
    return membres;
  }

  public void setMembres(Personne[] membres) {
    this.membres = membres;
  }

  public Map getGroupesDeTravail() {
    return groupesDeTravail;
  }

  public void setGroupesDeTravail(Map groupesDeTravail) {
    this.groupesDeTravail = groupesDeTravail;
  }

   // display
  public String toString() {
    String liste = "membres : ";
    for (int i = 0; i < this.membres.length; i++) {
      liste += "[" + this.membres[i].toString() + "]";
    }
    return liste + ", groupes de travail = " + this.groupesDeTravail.toString();
  }

   // init-close
  public void init() {
    System.out.println("init GroupePersonnes [" + this.toString() + "]");
  }

  public void close() {
    System.out.println("destroy GroupePersonnes [" + this.toString() + "]");
  }
}

Os seus dois membros privados são:

members: uma matriz de pessoas que são membros do grupo

workGroups: um dicionário que associa uma pessoa a um grupo de trabalho

Note-se aqui que a classe [PeopleGroup] não define um construtor sem argumentos para cumprir a norma JavaBean. Recorde-se que, na ausência de qualquer construtor, existe um construtor «padrão», que é o construtor sem argumentos que não faz nada.

O objetivo aqui é demonstrar como o Spring permite a inicialização de objetos complexos, tais como aqueles com campos de matriz ou dicionário. Adicionamos um novo bean ao ficheiro [config.xml] do Spring anterior:

    <bean id="groupe1" class="istia.st.springioc.domain.GroupePersonnes" 
        init-method="init" destroy-method="close">
        <property name="membres">
            <list>
                <ref bean="personne1"/>
                <ref bean="personne2"/>
            </list>
        </property>
        <property name="groupesDeTravail">
            <map>
                <entry key="Brigitte">
                    <value>Marketing</value>
                </entry>
                <entry key="Simon">
                    <value>Ressources humaines</value>
                </entry>
            </map>
        </property>
    </bean>
  1. A tag <list> permite-lhe inicializar um campo do tipo array ou um que implemente a interface List com valores diferentes.
  2. A tag <map> permite fazer o mesmo com um campo que implemente a interface Map

Para os nossos testes, utilizaremos a classe de teste JUnit já apresentada, adicionando-lhe o seguinte método [test3]:

1
2
3
4
5
  public void test3() {
    // bean retrieval [group1]]
    GroupePersonnes groupe1 = (GroupePersonnes) bf.getBean("groupe1");
    System.out.println("groupe1=" + groupe1.toString());
  }

O método [test3] recupera o bean [groupe1] e apresenta-o.

A estrutura do projeto Eclipse permanece a mesma do teste anterior. A execução do método [test3] do teste JUnit produz os seguintes resultados:

18 sept. 2004 15:51:45 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
18 sept. 2004 15:51:45 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'groupe1'
18 sept. 2004 15:51:45 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne1'
init personne [nom=[Simon], age=[40]]
18 sept. 2004 15:51:45 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne2'
init personne [nom=[Brigitte], age=[20]]
init GroupePersonnes [membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}]
groupe1=membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}

Comentários:

  • O método [test3] solicita uma referência ao bean [group1]
  • linha 4: O Spring começa a criar este bean
  • Como o bean [group1] faz referência aos beans [person1] e [person2], estes dois beans são criados (linhas 6 e 9) e os seus métodos init são executados (linhas 7 e 10)
  • Linha 11: O bean [group1] foi criado. O seu método [init] é agora executado.
  • Linha 12: Exibição solicitada pelo método [test3].

2.4. Spring para configurar aplicações web de três camadas

2.4.1. Arquitetura geral da aplicação

Queremos construir uma aplicação de três camadas com a seguinte estrutura:

  • As três camadas serão tornadas independentes através da utilização de interfaces Java
  • A integração das três camadas será gerida pelo Spring
  • Criaremos pacotes separados para cada uma das três camadas, aos quais daremos os nomes de Control, Domain e Dao. Um pacote adicional conterá as aplicações de teste.

A estrutura da aplicação no Eclipse poderá ser a seguinte:

Image

2.4.2. A camada de acesso a dados DAO

A camada DAO implementará a seguinte interface:

package istia.st.demo.dao;

public interface IDao1 {
  public int doSometingInDaoLayer(int a, int b);
}
  • Escreva duas classes, Dao1Impl1 e Dao1Impl2, que implementem a interface IDao1. O método Dao1Impl1.doSomethingInDaoLayer irá devolver a+b, e o método Dao1Impl2.doSomethingInDaoLayer irá devolver a-b.
  • Escreva uma classe de teste JUnit para testar as duas classes anteriores

2.4.3. A camada de negócios

A camada de negócios irá implementar a seguinte interface:

package istia.st.demo.domain;

public interface IDomain1 {
  public int doSomethingInDomainLayer(int a, int b);
}
  • Escreva duas classes, Domain1Impl1 e Domain1Impl2, que implementem a interface IDomain1. Estas classes terão um construtor que recebe um parâmetro do tipo IDao1. O método Domain1Impl1.doSomethingInDomainLayer incrementará a e b em um, e depois passará estes dois parâmetros para o método doSomethingInDaoLayer do objeto IDao1 recebido. O método Domain1Impl2.doSomethingInDomainLayer, por outro lado, irá diminuir a e b em um antes de fazer o mesmo.
  • Escreva uma classe de teste JUnit para testar as duas classes anteriores

2.4.4. A camada de interface do utilizador

A camada de interface do utilizador irá implementar a seguinte interface:

package istia.st.demo.control;

public interface IControl1 {
  public int doSometingInControlLayer(int a, int b);
}
  • Escreva duas classes, Control1Impl1 e Control1Impl2, que implementem a interface IControl1. Estas classes terão um construtor que recebe um parâmetro do tipo IDomain1. O método Control1Impl1.doSomethingInControlLayer incrementará a e b em um, e depois passará estes dois parâmetros para o método doSomethingInDomainLayer do objeto IDomain1 recebido. O método Control1Impl2.doSomethingInControlLayer, por outro lado, irá diminuir a e b em um antes de fazer o mesmo.
  • Escreva uma classe de teste JUnit para testar as duas classes anteriores

2.4.5. Integração com o Spring

  • Escreva um ficheiro de configuração Spring que determinará quais as classes que cada uma das três camadas anteriores deve utilizar
  • Escreva uma classe de teste JUnit utilizando diferentes configurações do Spring para destacar a flexibilidade da aplicação
  • Escreva uma aplicação autónoma (método main) que passe dois parâmetros para a interface IControl1 e exiba o resultado devolvido pela interface.

2.4.6. Uma solução

2.4.6.1. O projeto Eclipse

Image

Os arquivos na pasta [lib] foram adicionados ao [ClassPath] do projeto.

2.4.6.2. O pacote [istia.st.demo.dao]

A interface:

1
2
3
4
5
6
7
8
9
package istia.st.demo.dao;

/**
 * @author ST-ISTIA
 *  
 */
public interface IDao1 {
  public int doSometingInDaoLayer(int a, int b);
}

Uma primeira classe de implementação:

package istia.st.demo.dao;

/**
 * @author ST-ISTIA
 *  
 */
public class Dao1Impl1 implements IDao1 {

     // we do something in the [dao] layer
  public int doSometingInDaoLayer(int a, int b) {
    return a+b;
  }

}

Uma segunda classe de implementação:

package istia.st.demo.dao;

/**
 * @author ST-ISTIA
 *
 */
public class Dao1Impl2 implements IDao1 {

     // we do something in the [dao] layer
  public int doSometingInDaoLayer(int a, int b) {
    return a-b;
  }
}

2.4.6.3. O pacote [istia.st.demo.domain]

A interface:

package istia.st.demo.domain;

/**
 * @author ST-ISTIA
 *  
 */
public interface IDomain1 {

     // we do something in the [domain] layer
  public int doSomethingInDomainLayer(int a, int b);
}

Uma primeira classe de implementação:

package istia.st.demo.domain;

import istia.st.demo.dao.IDao1;

/**
 * @author ST-ISTIA
 *  
 */
public class Domain1Impl1 implements IDomain1 {

     // the [dao] layer access service
  private IDao1 dao1;

  public Domain1Impl1() {
     // constructor with no arguments
  }

     // memorizes the [dao] layer access service
  public Domain1Impl1(IDao1 dao1) {
    this.dao1 = dao1;
  }

     // we do something in the [domain] layer
  public int doSomethingInDomainLayer(int a, int b) {
    a++;
    b++;
    return dao1.doSometingInDaoLayer(a, b);
  }
}

Uma segunda classe de implementação:

package istia.st.demo.domain;

import istia.st.demo.dao.IDao1;

/**
 * @author ST-ISTIA
 *  
 */
public class Domain1Impl2 implements IDomain1 {

     // the [dao] layer access service
  private IDao1 dao1;

  public Domain1Impl2() {
     // constructor with no arguments
  }

     // memorizes the [dao] layer access service
  public Domain1Impl2(IDao1 dao1) {
    this.dao1 = dao1;
  }

     // we do something in the [domain] layer
  public int doSomethingInDomainLayer(int a, int b) {
    a--;
    b--;
    return dao1.doSometingInDaoLayer(a, b);
  }
}

2.4.6.4. O pacote [istia.st.demo.control]

A interface

1
2
3
4
5
6
7
8
9
package istia.st.demo.control;

/**
 * @author ST-ISTIA
 *  
 */
public interface IControl1 {
  public int doSometingInControlLayer(int a, int b);
}

Uma primeira classe de implementação:

package istia.st.demo.control;

import istia.st.demo.domain.IDomain1;

/**
 * @author ST-ISTIA
 *  
 */
public class Control1Impl1 implements IControl1 {
   // business class in layer [domain]
    private IDomain1 domain1;

  public Control1Impl1() {
     // constructor with no arguments
  }

     // domain] layer access service enhancement
  public Control1Impl1(IDomain1 domain1) {
    this.domain1 = domain1;
  }

     // we're doing something
  public int doSometingInControlLayer(int a, int b) {
    a++;
    b++;
    return domain1.doSomethingInDomainLayer(a, b);
  }

}

Uma segunda classe de implementação:

package istia.st.demo.control;

import istia.st.demo.domain.IDomain1;

/**
 * @author ST-ISTIA
 *  
 */
public class Control1Impl2 implements IControl1 {

     // the [domain] layer access class
    private IDomain1 domain1;

  public Control1Impl2() {
     // constructor with no arguments
  }

     // stores the [domain] layer access class
  public Control1Impl2(IDomain1 domain1) {
    this.domain1 = domain1;
  }

     // we're doing something
  public int doSometingInControlLayer(int a, int b) {
    a--;
    b--;
    return domain1.doSomethingInDomainLayer(a, b);
  }

}

2.4.6.5. [Spring] Ficheiros de configuração

Um primeiro [springMainTest1.xml]:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- the dao class -->
    <bean id="dao" class="istia.st.demo.dao.Dao1Impl1">
    </bean>
     <!-- the trade class -->
    <bean id="domain" class="istia.st.demo.domain.Domain1Impl1">
        <constructor-arg index="0">
            <ref bean="dao"/>
        </constructor-arg>
    </bean>
     <!-- the control class -->
    <bean id="control" class="istia.st.demo.control.Control1Impl1">
        <constructor-arg index="0">
            <ref bean="domain"/>
        </constructor-arg>
    </bean>
</beans>

Um segundo [springMainTest2.xml]:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- the dao class -->
    <bean id="dao" class="istia.st.demo.dao.Dao1Impl2">
    </bean>
     <!-- the trade class -->
    <bean id="domain" class="istia.st.demo.domain.Domain1Impl2">
        <constructor-arg index="0">
            <ref bean="dao"/>
        </constructor-arg>
    </bean>
     <!-- the control class -->
    <bean id="control" class="istia.st.demo.control.Control1Impl2">
        <constructor-arg index="0">
            <ref bean="domain"/>
        </constructor-arg>
    </bean>
</beans>

2.4.6.6. O pacote de testes [istia.st.demo.tests]

Um teste [principal]:

package istia.st.demo.tests;

import istia.st.demo.control.IControl1;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

/**
 * @author ST-ISTIA
 *  
 */
public class MainTest1 {
  public static void main(String[] arguments) {
     // we retrieve an implementation of the IControl1 interface
    IControl1 control = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest1.xml"))).getBean("control");
     // we use the
    int a = 10, b = 20;
    int res = control.doSometingInControlLayer(a, b);
     // the result is displayed
    System.out.println("control(" + a + "," + b + ")=" + res);
  }
}

Resultados apresentados na consola do Eclipse:

11 mars 2005 11:25:14 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [springMainTest1.xml]
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'control'
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'domain'
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'dao'
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'domain' instantiated via constructor [public istia.st.demo.domain.Domain1Impl1(istia.st.demo.dao.IDao1)]
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'control' instantiated via constructor [public istia.st.demo.control.Control1Impl1(istia.st.demo.domain.IDomain1)]
control(10,20)=34

Outro teste utilizando o segundo ficheiro de configuração [Spring]:

package istia.st.demo.tests;

import istia.st.demo.control.IControl1;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

/**
 * @author ST-ISTIA
 *  
 */
public class MainTest2 {
  public static void main(String[] arguments) {
     // we retrieve an implementation of the IControl1 interface
    IControl1 control = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest2.xml"))).getBean("control");
     // we use the
    int a = 10, b = 20;
    int res = control.doSometingInControlLayer(a, b);
     // the result is displayed
    System.out.println("control(" + a + "," + b + ")=" + res);
  }
}

Resultados apresentados na consola do Eclipse:

11 mars 2005 11:28:52 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [springMainTest2.xml]
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'control'
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'domain'
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'dao'
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'domain' instantiated via constructor [public istia.st.demo.domain.Domain1Impl2(istia.st.demo.dao.IDao1)]
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'control' instantiated via constructor [public istia.st.demo.control.Control1Impl2(istia.st.demo.domain.IDomain1)]
control(10,20)=-10

Por fim, um teste JUnit:

package istia.st.demo.tests;

import istia.st.demo.control.IControl1;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import junit.framework.TestCase;

/**
 * @author ST-ISTIA
 *  
 */
public class JunitTest2Control1 extends TestCase {
  public void testControl1() {
     // we retrieve an implementation of the IControl1 interface
    IControl1 control1 = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest1.xml"))).getBean("control");
     // we use the
    int a1 = 10, b1 = 20;
    int res1 = control1.doSometingInControlLayer(a1, b1);
    assertEquals(34, res1);
     // we retrieve another implementation of the IControl1 interface
    IControl1 control2 = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest2.xml"))).getBean("control");
     // we use the
    int a2 = 10, b2 = 20;
    int res2 = control2.doSometingInControlLayer(a2, b2);
    assertEquals(-10, res2);
  }
}

2.5. 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 Aspetos), 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 na camada de negócios que realiza duas operações inseparáveis sobre os dados (uma unidade de trabalho). Ele chama dois métodos, M1 e M2, na camada DAO para realizar essas duas operações.

Por residir na camada de negócios, o método M abstrai o armazenamento de dados subjacente. Por exemplo, não precisa de assumir que os dados estão armazenados num SGBD ou que deve incluir as chamadas aos métodos M1 e M2 dentro de uma transação do SGBD. Cabe à camada DAO tratar destes 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 através da configuração, podem ser alterados

Para além dos conceitos de IoC e AOP, o Spring fornece inúmeras classes de suporte para aplicações de três camadas:

  • para JDBC, SQLMap (iBatis), Hibernate e JDO (Java Data Object) na camada DAO
  • para o modelo MVC na camada de Interface do Utilizador

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