Skip to content

2. Artigo 1 - Spring IoC

Objetivos do 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 Controle), também conhecido como injeção de dependência (Dependency Injection)

2.1. Configurar uma aplicação de três camadas com o Spring

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

Suponhamos que o acesso às camadas de negócios e DAO seja 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 aos dados, ou camada DAO (Data Access Object), é comum trabalhar com um SGBD e, portanto, com um driver JDBC. Consideremos o esboço de uma classe que acessa uma tabela de artigos em um SGBD:

public class ArticlesDaoPlainJdbc implements IArticlesDao {

     // conexão com a fonte de dados
    private String driverClassName=null;
    private Connection connexion=null;
    private String url = null;
    private String user = null;
    private String pwd = null;
 ....

    public List getAllArticles() {
        // solicita-se a lista de itens
        try {
             // carregando o driver JDBC
            Class.forName(driverClassName);
            // criação de uma conexão com o BD
            connexion = DriverManager.getConnection(url, user, pwd);
            ...
        } catch (SQLException ex) {
            ...
        } finally {
            ...
        }
    }

Para realizar uma operação no SGBD, qualquer método precisa de um objeto [Connection] que represente a conexão com o banco de dados, por meio da qual ocorrerão as trocas entre ele e o código Java. Para construir esse objeto, são necessárias quatro informações:

String driverClassName
o nome da classe do driver JDBC do SGBD
String url
a URL JDBC do banco de dados a ser utilizado
String user
a identidade com a qual a conexão é estabelecida
String pwd
a senha dessa identidade

Como nossa classe [ArticlesDaoPlainJdbc] anterior pode obter essas informações? Existem várias possibilidades:

solução 1 — as informações estão codificadas diretamente na classe:

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

     // conexão com a fonte de dados
    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 dessa solução é que é necessário modificar o código Java sempre que ocorrer uma alteração nessas informações, por exemplo, a mudança da senha.

Solução 2 — as informações são passadas para o objeto durante sua construção:

public class ArticlesDaoPlainJdbc implements IArticlesDao {

     // conexão com a fonte de dados
    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, no momento de sua construção, as informações necessárias para funcionar. O problema passa então para o código que transmitiu as quatro informações. Como ele as obteve? A seguinte classe [ArticlesManagerWithDataBase] da camada de negócios poderia construir um objeto [ArticlesDaoPlainJdbc] da camada de acesso aos dados:

public class ArticlesManagerWithDataBase implements IArticlesManager {

    // uma instância de acesso aos dados
    private IArticlesDao articlesDao;
 ....
    public ArticlesManagerWithDataBase (String driverClassName, String url, String user, String pwd, ...) {
        ... 
         // criação do serviço de acesso aos dados
        articlesDao =(IArticlesDao)new ArticlesDaoPlainJdbc(driverClassName,url,user,pwd);
    ...
    }

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

Vemos que, mais uma vez, as informações necessárias para a construção do objeto [ArticlesDaoPlainJdbc] são fornecidas ao construtor do objeto [ArticlesManagerWithDataBase]. Podemos imaginar que elas lhe são transmitidas por uma camada superior, como a camada de interface com o usuário. Chega-se assim, passo a passo, à camada mais alta do aplicativo. Devido à sua posição, essa camada não é chamada por uma camada que pudesse transmitir-lhe as informações de configuração de que necessita. Portanto, é preciso encontrar uma solução alternativa à configuração por construtor. A solução habitual para configurar uma aplicação em sua camada mais alta é o uso de um arquivo onde se encontram todas as informações suscetíveis de mudar ao longo do tempo. Pode haver vários arquivos desse tipo. Ao iniciar a aplicação, uma camada de inicialização criará, então, todos ou parte dos objetos necessários às diferentes camadas da aplicação.

Existe uma grande variedade de arquivos de configuração. A tendência atual é utilizar arquivos XML. Essa é a opção adotada pelo Spring. O arquivo que configura um objeto [ArticlesDaoPlainJdbc] poderia ser o seguinte:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- a classe de acesso aos dados -->
    <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 de beans, pois eles seguem a norma JavaBean de nomenclatura dos acessadores e inicializadores (getters/setters) dos campos privados de um objeto. Os objetos que, em uma aplicação, têm a função de prestar um serviço são frequentemente criados em uma única instância. Eles são chamados de singletons. Assim, em nosso exemplo de aplicação multicamadas analisado aqui, o acesso ao banco de dados de artigos será garantido por uma única instância da classe [ArticlesDaoPlainJdbc]. Em uma aplicação web, esses objetos de serviço atendem a vários clientes ao mesmo tempo. Não se cria um objeto de serviço por cliente.

O arquivo de configuração do Spring acima permite criar um único objeto de serviço do tipo [ArticlesDaoPlainJdbc] em um pacote chamado [istia.st.articles.dao]. As quatro informações necessárias para o construtor desse objeto são definidas dentro de uma tag <bean>...</bean>. Haverá tantas tags <bean> quanto singletons a serem construídos.

Em que momento ocorrerá a construção dos objetos definidos no arquivo do Spring? A inicialização de uma aplicação pode ser atribuída ao método main dessa mesma aplicação, caso ela possua um. Para uma aplicação web, pode ser o método [init] do servlet principal. Em toda aplicação, há um método que certamente será o primeiro a ser executado. Geralmente, é nesse método que ocorre a construção dos singletons.

Vejamos um exemplo. Suponhamos que queiramos testar a classe [ArticlesDaoPlainJdbc] mencionada anteriormente por meio de um teste JUnit. Uma classe de teste JUnit possui um método [setUp] executado antes de qualquer outro método. É nesse método que criaremos o singleton [ArticlesDaoPlainJdbc].

Se seguirmos a solução de passar as informações de configuração por meio do construtor, teremos a seguinte classe de teste:

public class TestArticlesPlainJdbc extends TestCase {
    // testa a classe de acesso aos itens ArticlesDaoPlainJdbc
     // a fonte de dados está definida no sprintest

     // uma instância da classe testada
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception{
        // recupera uma instância de acesso aos dados
        articlesDao =
            (IArticlesDao) new ArticlesDaoPlainJdbc("org.firebirdsql.jdbc.FBDriver",
                "jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb","someone","somepassword");
    }

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

Se seguirmos a solução de passar as informações de configuração por meio de um arquivo de configuração, poderíamos ter a seguinte classe de teste utilizando o arquivo Spring descrito acima.

public class TestSpringArticlesPlainJdbc extends TestCase {
    // testa a classe de acesso aos itens ArticlesDaoJdbc
     // a fonte de dados está definida no sprintest

     // uma instância da classe testada
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception {
      // recupera uma instância de acesso aos dados
      articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
          "springArticlesPlainJdbc.xml"))).getBean("articlesDao");
    }

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

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

Uma alteração no arquivo de configuração, fora dessas duas entidades, não tem nenhum impacto no código Java. Esse método de configuração dos objetos de uma aplicação é muito flexível. Para se configurar, a aplicação precisa saber apenas duas coisas:

  • o nome do arquivo Spring que contém a definição dos singletons a serem criados
  • os nomes desses singletons, que servem ao código Java para obter uma referência aos objetos aos quais foram associados por meio do arquivo de configuração

2.2. Injeção de dependências e inversão de controle

Vamos agora introduzir o conceito de injeção de dependência (Dependency Injection) utilizado pelo Spring para configurar as aplicações. Também se utiliza o termo inversão de controle (IoC, Inversion of Control). Consideremos a criação do singleton [ArticlesManagerWithDataBase] da camada de negócios da nossa aplicação:

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

public class ArticlesManagerWithDataBase implements IArticlesManager {

     // uma instância de acesso aos dados
    private IArticlesDao articlesDao;
 ....
    public ArticlesManagerWithDataBase (String driverClassName, String url, String user, String pwd, ...) {
        ... 
         // criação do serviço de acesso aos dados
        articlesDao =(IArticlesDao)new ArticlesDaoPlainJdbc(driverClassName,url,user,pwd);
    ...
    }

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

A classe [ArticlesDaoPlainJdbc] deve, neste caso, implementar uma interface [IArticlesDao]:

public class ArticlesDaoPlainJdbc implements IArticlesDao {...}

Para criar o singleton do tipo [IArticlesDao] necessário para o funcionamento da classe, o construtor desta utiliza explicitamente o nome da classe de implementação da interface [IArticlesDao]:

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

Portanto, há uma dependência rígida no código em relação ao nome da classe. Se a classe de implementação da interface [IArticlesDao] vier a mudar, o código do construtor anterior precisaria ser alterado. Temos as seguintes relações entre os objetos:

A classe [ArticlesManagerWithDataBase] toma por conta própria a iniciativa de criar o objeto [ArticlesDaoPlainJdbc] de que necessita. Voltando ao termo “inversão de controle”, pode-se dizer que é ela quem tem o “controle” para criar o objeto de que necessita.

Se tivéssemos que escrever uma classe de teste JUnit para a classe [ArticlesManagerWithDataBase], poderíamos ter algo como o seguinte:

public class TestArticlesManagerWithDataBase extends TestCase {
    // uma instância da classe de negócios testada
    private IArticlesManager articlesManager;

    protected void setUp() throws Exception {
        // cria uma instância da classe de negócios testada
        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ócios [ArticlesManagerWithDataBase], que, por sua vez, cria, em seu construtor, uma instância da classe de acesso a dados [ArticlesDaoPlainJdbc].

A solução com o Spring eliminará a necessidade de a classe de negócios [ArticlesManagerWithDataBase] conhecer o nome [ArticlesDaoPlainJdbc] da classe de acesso aos dados de que ela precisa. Isso permitirá alterá-la sem mexer no código Java da classe de negócios. O Spring permitirá criar simultaneamente os dois singletons: o da camada de acesso aos dados e o da camada de negócios. O arquivo de configuração do Spring definirá um novo bean:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- a classe de acesso aos dados -->
    <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 novidade está no bean que define o singleton da classe de negócios a ser criado:

    <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. o campo [articlesDao] do bean recebe um valor por meio da tag <property name="articlesDao">. Trata-se do campo definido na classe [ArticlesManagerWithDataBase]:
public class ArticlesManagerWithDataBase implements IArticlesManager {

  // interface de acesso aos dados
  private IArticlesDao articlesDao;

  public IArticlesDao getArticlesDao() {
    return articlesDao;
  }

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

Para que o campo [articlesDao] possa ser inicializado pelo Spring e sua tag <property>, é necessário que o campo siga a norma JavaBean e que exista um método [setArticlesDao] para inicializar o campo [articlesDao]. Observe-se que o nome do método é derivado de forma bem precisa do nome do campo. Paralelamente, geralmente existe um método [get...] para obter o valor do campo. Aqui, trata-se do método [getArticlesDao]. Nesta nova versão, a classe [ArticlesManagerWithDataBase] não possui mais um construtor. Ela não precisa mais dele.

  • O valor que será atribuído ao campo [articlesDao] pelo Spring é o do bean [articlesDao] definido em seu arquivo 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 criar o singleton [ArticlesManagerWithDataBase], ele também criará o singleton [ArticlesDaoPlainJdbc]:
    • O Spring criará um gráfico de dependências dos beans e verificará que o bean [articlesManager] depende do bean [articlesDao]
    • ele criará o bean [articlesDao], ou seja, um objeto do tipo [ArticlesDaoPlainJdbc]
    • em seguida, criará o bean [articlesManager] do tipo [ArticlesManagerWithDataBase]

Imaginemos agora um teste JUnit para a classe [ArticlesManagerWithDataBase]. Ele poderia ser semelhante ao seguinte:

public class TestSpringArticlesManagerWithDataBase extends TestCase {
    // testa a classe de negócios [ArticlesManagerWithDataBase]

    // uma instância da classe de negócios testada
    private IArticlesManager articlesManager;

    protected void setUp() throws Exception {
      // recupera uma instância de acesso aos dados
      articlesManager = (IArticlesManager) (new XmlBeanFactory(new ClassPathResource(
          "springArticlesManagerWithDataBase.xml"))).getBean("articlesManager");
    }

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

  • O método [setUp] acima solicita uma referência ao bean chamado [articlesManager]
  • O Spring consulta seu arquivo de configuração e encontra o bean [articlesManager]. Se ele já estiver criado, o Spring simplesmente retorna uma referência ao objeto (singleton); caso contrário, ele o cria.
  • O Spring identifica a dependência do bean [articlesManager] em relação ao bean [articlesDao]. Portanto, ele cria o singleton [articlesDao] do tipo [ArticlesDaoPlainJdbc], caso este ainda não tenha sido criado (singleton).
  • Ele cria o singleton [articlesManager] do tipo [ArticlesManagerWithDataBase]

Esse mecanismo poderia ser esquematizado da seguinte forma:

Recordemos a estrutura da classe [ArticlesManagerWithDataBase]:

public class ArticlesManagerWithDataBase implements IArticlesManager {

  // interface de acesso aos dados
  private IArticlesDao articlesDao;

  public IArticlesDao getArticlesDao() {
    return articlesDao;
  }

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

Ao final da construção dos singletons pelo Spring, temos um objeto do tipo [ArticlesManagerWithDataBase] cujo campo [articlesDao] foi inicializado sem que ele saiba como. Diz-se que injetamos uma dependência no objeto [ArticlesManagerWithDataBase]. Diz-se também que houve inversão de controle: não é mais o objeto [ArticlesManagerWithDataBase] que toma a iniciativa de criar por conta própria o objeto que implementa a interface [IArticlesDao] de que necessita, mas sim a aplicação no nível mais alto (ao ser inicializada) que se encarrega de criar todos os objetos de que necessita, gerenciando as dependências entre eles.

A principal vantagem da configuração do singleton [ArticlesManagerWithDataBase] por meio de um arquivo Spring é que agora é possível alterar a classe de implementação correspondente ao campo [articlesDao] da classe [ArticlesManagerWithDataBase] sem que o código desta última seja modificado. Basta alterar o nome da classe na definição do bean [articlesDao] no arquivo 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 essa nova classe de acesso a dados, sem nem mesmo perceber.

2.3. Spring IoC na prática

2.3.1. Exemplo 1

Consideremos a seguinte classe:

package istia.st.springioc.domain;

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

   // exibição Pessoa
  public String toString() {
    return "nom=[" + this.nom + "], age=[" + this.age + "]";
  }

   // inicialização e fechamento
  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 possui:

  • dois campos privados, nome e idade
  • os métodos de leitura (get) e gravação (set) desses dois campos
  • um método toString para recuperar o valor do objeto [Personne] na forma de uma sequência de caracteres
  • um método `init`, que será chamado pelo Spring na criação do objeto, e um método `close`, que será chamado na destruição do objeto

Para criar objetos do tipo [Personne], utilizaremos o seguinte arquivo 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>

Esse arquivo se chamará config.xml.

  • Ele define dois beans com as chaves respectivas “pessoa1” e “pessoa2”, do tipo [Personne]
  • Ele inicializa os campos [nom, age] de cada pessoa
  • Ele 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 nossos testes, utilizaremos uma única classe de teste JUnit, à qual adicionaremos métodos sucessivamente. A primeira versão dessa 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 {

   // fábrica de beans
  private ListableBeanFactory bf;

   // inicialização de testes
  public void setUp() {
    bf = new XmlBeanFactory(new ClassPathResource("config.xml"));
  }

  public void test1() {
    // recuperação dos beans [Personne] pelo respectivo código-chave do arquivo Spring
    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 obter os beans definidos no arquivo [config.xml], utilizamos um objeto do tipo [ListableBeanFactory]. Existem outros tipos de objetos que permitem acessar os beans. O objeto [ListableBeanFactory] é obtido no método [setUp] da classe de teste e armazenado em uma variável privada. Assim, ele estará disponível para todos os métodos de teste.
  • O arquivo [config.xml] será colocado no [ClassPath] do aplicativo, c.a.d, em um dos diretórios explorados pela máquina virtual Java quando ela procura uma classe referenciada pelo aplicativo. O objeto [ClassPathResource] serve para procurar um recurso no [ClassPath] de um aplicativo, neste caso, o arquivo [config.xml].
  • O Spring pode utilizar arquivos de configuração em diversos formatos. O objeto [XmlBeanFactory] permite analisar um arquivo de configuração no formato XML.
  • A análise de um arquivo Spring resulta em um objeto do tipo [ListableBeanFactory], neste caso, o objeto bf. Com esse objeto, um bean identificado pela chave C é obtido por meio de bf.getBean(C).
  • O método [test1] solicita e exibe o valor dos beans com as chaves “pessoa1” e “pessoa2”.

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

Image

Comentários:

  • a pasta [src] contém os códigos-fonte. Os códigos compilados serão colocados na pasta [bin], que não está representada aqui.
  • O arquivo [config.xml] está na raiz da pasta [src]. A compilação do projeto o copia automaticamente para a pasta [bin], que faz parte da pasta [ClassPath] do aplicativo. É lá que ele é procurado pelo objeto [ClassPathResource].
  • A pasta [lib] contém três bibliotecas Java necessárias para o aplicativo:
      • 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] do aplicativo

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 registra uma série de eventos por meio da biblioteca [commons-logging.jar]. Esses registros nos permitem compreender melhor o funcionamento do Spring.
  • O arquivo [config.xml] foi carregado e, em seguida, processado
  • a operação*
Personne personne1 = (Personne) bf.getBean("personne1");

forçou a criação do bean [personne1]. Podemos ver o log do Spring a respeito disso. Como na definição do bean [personne1] havia sido escrito [init-method="init"], o método [init] do objeto [Personne] criado foi executado. A mensagem correspondente é exibida.

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

fez com que o valor do objeto [Personne] criado fosse exibido.

  • O mesmo fenômeno se repete para o bean-chave [personne2].
  • 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 [Personne]. Se tivesse sido o caso, teríamos visto a exibição do método [init], o que não ocorre aqui. Esse é o princípio do singleton. Por padrão, o Spring cria apenas uma instância dos beans do seu arquivo de configuração. Trata-se de um serviço de referências a objetos. Se for solicitada a referência de um objeto ainda não criado, ele o cria e retorna uma referência. Se o objeto já tiver sido criado, o Spring se limita a fornecer uma referência.

  • Pode-se observar que não há nenhum vestígio do método [close] do objeto [Personne], embora tenhamos escrito na definição dos beans [destroy-method=close]. É possível que esse método só seja executado quando a memória ocupada pelo objeto for recuperada pelo coletor de lixo (garbage collector). No momento em que isso ocorre, a aplicação já está encerrada e a exibição na tela não tem nenhum efeito. A ser verificado.

Agora que já dominamos os fundamentos de uma configuração do Spring, nossas explicações serão um pouco mais rápidas daqui em diante.

2.3.2. Exemplo 2

Consideremos a seguinte nova classe [Voiture]:

package istia.st.springioc.domain;

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

   // construtores

  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 e 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;
  }

   // inicialização e fechamento
  public void init() {
    System.out.println("init voiture [" + this.toString() + "]");
  }

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

}

A classe apresenta:

  • três campos privados: tipo, marca e proprietário. Esses campos podem ser inicializados e lidos por meio dos métodos públicos get e set dos beans. Eles também podem ser inicializados usando o construtor Carro(String, String, Pessoa). A classe possui ainda um construtor sem argumentos, a fim de seguir a norma JavaBean.
  • um método toString para recuperar o valor do objeto [Voiture] na forma de uma cadeia de caracteres
  • um método init que será chamado pelo Spring logo após a criação do objeto, e um método close que será chamado na destruição do objeto

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

<?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 arquivo adiciona às definições anteriores um bean com a chave “voiture1” do tipo [Voiture]. Para inicializar esse 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 esse método já apresentado, optamos aqui por utilizar o construtor Carro(String, String, Pessoa) da classe. Além disso, o bean [voiture1] define o método a ser chamado durante a construção inicial do objeto [init-method] e aquele a ser chamado durante a destruição do objeto [destroy-method].

Para 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() {
     // recuperação do bean [voiture1]
    Voiture Voiture1 = (Voiture) bf.getBean("voiture1");
    System.out.println("Voiture1=" + Voiture1.toString());
  }

O método [test2] recupera o bean [voiture1] e o exibe.

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 [voiture1]
  2. linha 4: o Spring inicia a criação do bean [voiture1], pois esse bean ainda não foi criado (singleton)
  3. linha 6: como o bean [voiture1] faz referência ao bean [personne2], este último bean é, por sua vez, instanciado
  4. linha 7: o bean [personne2] foi criado. Seu método [init] é então executado.
  5. linha 9: o Spring indica que utilizará um construtor para criar o bean [voiture1]
  6. linha 10: o bean [voiture1] foi criado. Seu método [init] é então executado.
  7. linha 11: o método [test2] exibe o valor do bean [voiture1]

2.3.3. Exemplo 3

Introduzimos a seguinte nova classe [GroupePersonnes]:

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;
  }

   // exibição
  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();
  }

   // inicialização e fechamento
  public void init() {
    System.out.println("init GroupePersonnes [" + this.toString() + "]");
  }

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

Seus dois membros privados são:

membros: um array de pessoas que fazem parte do grupo

groupesDeTravail: um dicionário que atribui uma pessoa a um grupo de trabalho

Observe-se aqui que a classe [GroupePersonnes] não define um construtor sem argumentos para seguir a norma JavaBean. Vale lembrar que, na ausência de qualquer construtor, existe um construtor “padrão”, que é o construtor sem argumentos e que não realiza nenhuma ação.

O objetivo aqui é mostrar como o Spring permite inicializar objetos complexos, como aqueles que possuem campos do tipo matriz ou dicionário. Adicionamos um novo bean ao arquivo Spring [config.xml] 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 inicializar um campo do tipo array ou que implemente a interface List com diferentes valores.
  2. A tag <map> permite fazer o mesmo com um campo que implemente a interface Map

Para 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() {
    // recuperação do bean [groupe1]
    GroupePersonnes groupe1 = (GroupePersonnes) bf.getBean("groupe1");
    System.out.println("groupe1=" + groupe1.toString());
  }

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

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 [groupe1]
  • linha 4: o Spring inicia a criação desse bean
  • como o bean [groupe1] faz referência aos beans [personne1] e [personne2], esses dois beans são criados (linhas 6 e 9) e seus métodos init são executados (linhas 7 e 10)
  • linha 11: o bean [groupe1] foi criado. 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

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

  • as três camadas serão tornadas independentes por meio do uso de interfaces Java
  • A integração das três camadas será realizada pelo Spring
  • Serão criados pacotes separados para cada uma das três camadas, que serão denominados Control, Domain e Dao. Um pacote adicional conterá os aplicativos de teste.

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

Image

2.4.2. A camada DAO de acesso aos dados

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 retornará a+b, e o método Dao1Impl2. doSomethingInDaoLayer retornará a-b.
  • escrever uma classe de teste JUnit para testar as duas classes anteriores

2.4.3. A camada de negócios

A camada de negócios 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. Essas classes terão um construtor que receba como parâmetro um objeto do tipo IDao1. O método Domain1Impl1.doSomethingInDomainLayer incrementará a e b em uma unidade e, em seguida, passará esses dois parâmetros para o método doSomethingInDaoLayer do objeto do tipo IDao1 recebido. Já o método Domain1Impl2.doSomethingInDomainLayer diminuirá a e b em uma unidade antes de fazer o mesmo.
  • Escrever uma classe de teste JUnit para testar as duas classes anteriores

2.4.4. A camada de interface do usuário

A camada de interface do usuário 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. Essas classes terão um construtor que recebe um parâmetro do tipo IDomain1. O método Control1Impl1.doSomethingInControlLayer incrementará a e b em uma unidade e, em seguida, passará esses dois parâmetros para o método doSomethingInDomainLayer do objeto do tipo IDomain1 recebido. Já o método Control11Impl2.doSomethingInControlLayer diminuirá a e b em uma unidade antes de fazer o mesmo.
  • Escrever uma classe de teste JUnit para testar as duas classes anteriores

2.4.5. Integração com o Spring

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

2.4.6. Uma solução

2.4.6.1. O projeto Eclipse

Image

Os arquivos da 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 {

     // fazemos algo na camada [dao]
  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 {

     // faz-se algo na camada [dao]
  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 {

     // fazemos algo na camada [domain]
  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 {

     // o serviço de acesso à camada [dao]
  private IDao1 dao1;

  public Domain1Impl1() {
     // construtor sem argumento
  }

     // armazena o serviço de acesso à camada [dao]
  public Domain1Impl1(IDao1 dao1) {
    this.dao1 = dao1;
  }

     // realiza alguma ação na camada [domain]
  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 {

     // o serviço de acesso à camada [dao]
  private IDao1 dao1;

  public Domain1Impl2() {
     // construtor sem argumento
  }

     // armazena o serviço de acesso à camada [dao]
  public Domain1Impl2(IDao1 dao1) {
    this.dao1 = dao1;
  }

     // realiza alguma ação na camada [domain]
  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 {
   // classe de negócio na camada [domain]
    private IDomain1 domain1;

  public Control1Impl1() {
     // construtor sem argumento
  }

     // memorização do serviço de acesso à camada [domain]
  public Control1Impl1(IDomain1 domain1) {
    this.domain1 = domain1;
  }

     // fazemos alguma coisa
  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 {

     // a classe de acesso à camada [domain]
    private IDomain1 domain1;

  public Control1Impl2() {
     // construtor sem argumento
  }

     // armazena a classe de acesso à camada [domain]
  public Control1Impl2(IDomain1 domain1) {
    this.domain1 = domain1;
  }

     // fazemos alguma coisa
  public int doSometingInControlLayer(int a, int b) {
    a--;
    b--;
    return domain1.doSomethingInDomainLayer(a, b);
  }

}

2.4.6.5. Os arquivos de configuração [Spring]

Um primeiro [springMainTest1.xml]:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- a classe DAO -->
    <bean id="dao" class="istia.st.demo.dao.Dao1Impl1">
    </bean>
     <!-- a classe de negócio -->
    <bean id="domain" class="istia.st.demo.domain.Domain1Impl1">
        <constructor-arg index="0">
            <ref bean="dao"/>
        </constructor-arg>
    </bean>
     <!-- a classe de controle -->
    <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>
     <!-- a classe DAO -->
    <bean id="dao" class="istia.st.demo.dao.Dao1Impl2">
    </bean>
     <!-- a classe de negócio -->
    <bean id="domain" class="istia.st.demo.domain.Domain1Impl2">
        <constructor-arg index="0">
            <ref bean="dao"/>
        </constructor-arg>
    </bean>
     <!-- a classe de controle -->
    <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 do tipo [main]:

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) {
     // recuperamos uma implementação da interface IControl1
    IControl1 control = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest1.xml"))).getBean("control");
     // utiliza-se a classe
    int a = 10, b = 20;
    int res = control.doSometingInControlLayer(a, b);
     // exibe-se o resultado
    System.out.println("control(" + a + "," + b + ")=" + res);
  }
}

Os resultados obtidos no console 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 arquivo 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) {
     // recupera-se uma implementação da interface IControl1
    IControl1 control = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest2.xml"))).getBean("control");
     // utiliza-se a classe
    int a = 10, b = 20;
    int res = control.doSometingInControlLayer(a, b);
     // exibe-se o resultado
    System.out.println("control(" + a + "," + b + ")=" + res);
  }
}

Resultados obtidos no console 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() {
     // recupera-se uma implementação da interface IControl1
    IControl1 control1 = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest1.xml"))).getBean("control");
     // utiliza-se a classe
    int a1 = 10, b1 = 20;
    int res1 = control1.doSometingInControlLayer(a1, b1);
    assertEquals(34, res1);
     // recupera-se outra implementação da interface IControl1
    IControl1 control2 = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest2.xml"))).getBean("control");
     // utiliza-se a classe
    int a2 = 10, b2 = 20;
    int res2 = control2.doSometingInControlLayer(a2, b2);
    assertEquals(-10, res2);
  }
}

2.5. Conclusion

O framework Spring oferece uma flexibilidade real tanto na arquitetura das aplicações quanto em sua configuração. Utilizamos o conceito IoC, um dos dois pilares do Spring. O outro pilar é o AOP (Programação Orientada a Aspectos), que não apresentamos. Ele permite adicionar, por meio de configuração, um “comportamento” a um método de classe sem alterar o código da mesma. Esquematicamente, o AOP permite filtrar as chamadas a determinados métodos:

  • o filtro pode ser executado antes ou depois do método M alvo, ou ambos.
  • O método M ignora a existência desses filtros. Eles são definidos no arquivo de configuração do Spring.
  • O código do método M não é alterado. Os filtros são classes Java a serem criadas. O Spring fornece filtros predefinidos, especialmente para gerenciar as transações do SGBD.
  • Os filtros são beans e, como tal, são definidos no arquivo de configuração do Spring como beans.

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

Por estar na camada de negócios, o método M abstrai-se do suporte desses dados. Ele não precisa, por exemplo, de partir do pressuposto de que os dados estão em um SGBD e que precisa colocar as duas chamadas aos métodos M1 e M2 dentro de uma transação de SGBD. Cabe à camada DAO cuidar desses detalhes. Uma solução para o problema anterior seria, então, criar um método na camada DAO que, por sua vez, chamasse os métodos M1 e M2, chamadas que ela incluiria em uma transação de SGBD.

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

Essa abordagem apresenta várias vantagens:

  • uma vez definido o filtro, ele pode ser aplicado a vários métodos, por exemplo, todos aqueles que necessitam de uma transação
  • os métodos assim filtrados não precisam ser reescritos
  • como os filtros a serem usados são definidos por configuração, é possível alterá-los

Além dos conceitos IoC e AOP, o Spring oferece diversas classes de suporte para aplicações de três camadas:

  • para JDBC, SqlMap (iBatis), Hibernate, JDO (Java Data Object) na camada DAO
  • para o modelo MVC na camada de interface do usuário

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