Skip to content

2. Introdução ao framework Spring

O Spring surgiu em 2004, inicialmente como um contêiner de objetos. Desde então, evoluiu para várias ramificações: Spring MVC, Spring Data, Spring Batch, ... [http://spring.io]. Neste capítulo, apresentamos apenas o contêiner de objetos. Aqui estão alguns pontos de referência:

  • uma aplicação possui várias classes e algumas delas compartilham objetos que devem ser únicos (singletons). O Spring cria e gerencia esses singletons;
  • o Spring coloca esses singletons em uma estrutura chamada contexto;
  • as classes têm acesso aos singletons da aplicação solicitando-os ao Spring por meio de seu nome, tipo ou ambos;
  • o Spring cria os singletons e gerencia suas eventuais dependências: um singleton pode, de fato, ter referências a um ou mais outros singletons. Quando o Spring cria um singleton, ele também cria suas dependências;
  • quando uma aplicação baseada no Spring é iniciada, ela pode solicitar ao Spring que crie todos os singletons da aplicação. Estes ficarão então disponíveis no contexto do Spring;
  • O Spring facilita o uso de arquiteturas em camadas e a programação por interfaces. Em casos simples, cada camada é implementada por um singleton e implementa uma interface. Se a aplicação trabalhar com as interfaces das camadas e não com suas classes de implementação, obtém-se uma arquitetura escalável que permite alterar a implementação de uma camada sem alterar as outras, graças às duas características a seguir:
    • a aplicação obtém uma referência à camada por meio de seu nome. O Spring fornece a ela uma referência à classe que implementa a camada;
    • a aplicação utiliza essa referência como se fosse a da interface da camada e não como se fosse a de uma classe;

A declaração de singletons pode ser feita de três maneiras, que podem ser combinadas:

  • dentro de um arquivo XML,
  • em uma classe especial de configuração;
  • com qualquer classe por meio de anotações;

Apresentamos a seguir três exemplos de configuração:

  • [exemple-01]: configuração centralizada em um único arquivo XML;
  • [exemple-02]: configuração centralizada em uma única classe Java;
  • [exemple-03]: configuração distribuída por várias classes Java;

O último exemplo, [exemple-04], aborda a configuração Spring de uma arquitetura em camadas. Esse é o exemplo mais importante. É ele que será utilizado constantemente para configurar as arquiteturas deste documento.

Esses quatro exemplos estabelecem as bases para o que se segue:

  • configuração do Spring e injeção de dependências;
  • uso do Maven para gerenciar as dependências de um projeto;
  • uso do JUnit para testar os projetos;

2.1. Configuração do ambiente de trabalho

Você deve ter:

  • ter instalado o JDK (Java Development Kit) (parágrafo 23.1);
  • ter instalado o gerenciador de dependências Maven (parágrafo 23.2);
  • ter instalado o IDE Spring Tool Suite (STS) (parágrafo 23.3);
  • baixou os códigos do documento [http://tahe.developpez.com/java/spring-database];

Importe para o STS as configurações de execução da pasta [eclipse config] dos exemplos. Essas configurações são particularmente importantes. Alguns projetos exigem, para serem executados, que sejam passados argumentos ao JVM, e esse tipo de configuração costuma ser um desafio. Além disso, este documento utiliza projetos Maven. Quando você encontrar o aviso:

Observação: execute o [Alt-F5] para regenerar todos os projetos Maven.

É altamente recomendável seguir essa orientação. Sem essa precaução, os projetos podem apresentar erros incompreensíveis simplesmente porque as dependências do Maven entre os projetos estão incorretas.

  • em [1], clique com o botão direito do mouse em [Package Explorer];
  • em [4a-4b-4c], selecione a pasta [eclipse config / launch configurations] [4b] de exemplos;
  • em [5], as configurações disponíveis. Selecione todas;
  • em [6], conclua o assistente;
  • em [7-8], visualize as configurações de execução importadas;
  • em [8-9], desmarque [9] para exibir as configurações de projetos não carregados em STS. Esse é o caso atualmente;
  • em [10], os aplicativos Java, e em [11], as configurações de execução dos três primeiros exemplos que vamos estudar;
  • em [12], os testes JUnit e em [13], a configuração de execução do quarto exemplo deste parágrafo;

Agora, crie uma variável no Eclipse chamada [M2_REPO], que indicará a pasta do repositório local do Maven (ver parágrafo 23.2). Essa variável é utilizada em várias configurações de execução:

A variável [M2_REPO] recebeu o valor visível em [6] abaixo:

Agora, importe os quatro exemplos da pasta [spring-core]:

  • para [1], clique com o botão direito do mouse em [Package Explorer];
  • para [4a-4b], selecione a pasta [spring-core] dos exemplos;
  • em [5], selecione todos os projetos da pasta e execute [Finish];
  • em [6], os quatro projetos em [Package Explorer];

2.2. Exemple-01

2.2.1. O projeto Eclipse

 

2.2.2. A classe [Personne]

 

package istia.st.spring.core;

public class Personne {

    // campos
    private String nom;
    private String prenom;
    private int age;

    // construtores
    public Personne() {

    }

    public Personne(String nom, String prénom, int âge) {
        this.nom = nom;
        this.prenom = prénom;
        this.age = âge;
    }

    // toString
    public String toString() {
        return String.format("Personne[%s, %s,%d]", prenom, nom, age);
    }

    // getters e setters

    public String getNom() {
        return nom;
    }

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

    public String getPrenom() {
        return prenom;
    }

    public void setPrenom(String prenom) {
        this.prenom = prenom;
    }

    public int getAge() {
        return age;
    }

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

}

Observação: os getters e setters podem ser gerados automaticamente da seguinte maneira [1-2]:

2.2.3. A classe [Appartement]

 

package istia.st.spring.core;

public class Appartement {

    // campos
    private Personne proprietaire;
    private int surface;

    // getters e setters

    public Personne getProprietaire() {
        return proprietaire;
    }

    public void setProprietaire(Personne proprietaire) {
        this.proprietaire = proprietaire;
    }

    public int getSurface() {
        return surface;
    }

    public void setSurface(int surface) {
        this.surface = surface;
    }

    // toString
    public String toString() {
        return String.format("Appartement[%s, %s]", proprietaire, surface);
    }

}

Observação: essa classe não possui um construtor explícito. Nesse caso, existe sempre, por padrão, o construtor sem parâmetros, que não realiza nenhuma ação. Quando se criam construtores, esse construtor padrão deixa de existir implicitamente. É necessário, então, defini-lo explicitamente:

public Appartement(){
}

2.2.4. O arquivo de configuração do Spring

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util-4.0.xsd">
    <!-- Pessoa 01 -->
    <bean id="personne_01" class="istia.st.spring.core.Personne">
        <constructor-arg index="0" value="dubois" />
        <constructor-arg index="1" value="paul" />
        <constructor-arg index="2" value="34" />
    </bean>
    <!-- Pessoa 02 -->
    <bean id="personne_02" class="istia.st.spring.core.Personne">
        <property name="nom" value="martin" />
        <property name="prenom" value="micheline" />
        <property name="age" value="18" />
    </bean>
    <!-- uma lista de pessoas -->
    <util:list id="club">
        <ref bean="personne_01" />
        <ref bean="personne_02" />
    </util:list>
    <!-- um apartamento -->
    <bean id="appartement" class="istia.st.spring.core.Appartement">
        <property name="surface" value="100" />
        <property name="proprietaire" ref="personne_01" />
    </bean>
</beans>
  • linhas 2, 27: os singletons são definidos dentro de uma tag <beans>;
  • linhas 6-10: cada singleton é definido por uma tag <bean>;
  • linha 6: [id] é o identificador do singleton. [class] é o nome completo da classe a ser instanciada;
  • linhas 7-9: os três valores a serem passados ao construtor da classe [Personne];
  • linhas 12-16: a classe [Personne] é criada inicialmente com seu construtor padrão [new Personne()]. Em seguida, para cada tag [property], é utilizado um setter da classe. Por exemplo, na linha 13, o método [setNom("martin")] será executado. Portanto, é necessário que o método [setNom] exista. É importante lembrar disso;
  • linhas 18-21: a tag <util:list> permite definir um singleton que é uma lista;
  • linha 19: refere-se ao singleton [personne_01] definido na linha 6. Trata-se do que chamamos de injeção de dependências. Dois atributos podem ser utilizados para inicializar o campo de um singleton:
    • [value]: para atribuir ao campo um valor primitivo (cadeia de caracteres, número, data, etc.),
    • [ref]: para atribuir ao campo a referência de um objeto Spring;

Observação: o arquivo de configuração do Spring pode ser gerado da seguinte maneira [1-4]:

2.2.5. A classe executável

 

package istia.st.spring.core;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Demo01 {

    @SuppressWarnings({ "unchecked", "resource" })
    public static void main(String[] args) {
        // recuperação do contexto Spring
        ApplicationContext ctx = new ClassPathXmlApplicationContext("config-01.xml");
        // recuperamos os beans
        Personne p01 = ctx.getBean("personne_01", Personne.class);
        Personne p02 = ctx.getBean("personne_02", Personne.class);
        List<Personne> club = ctx.getBean("club", new ArrayList<Personne>().getClass());
        Appartement appart01 = ctx.getBean(Appartement.class);
        // eles são exibidos
        System.out.println("personnes--------");
        System.out.println(p01);
        System.out.println(p02);
        System.out.println("club--------");
        for (Personne p : club) {
            System.out.println(p);
        }
        System.out.println("appartement--------");
        System.out.println(appart01);
        // os beans recuperados são singletons
        // é possível solicitá-los várias vezes; sempre se recupera o mesmo bean
        Personne p01b = ctx.getBean("personne_01", Personne.class);
        System.out.println(String.format("beans [p01,p01b] identiques ? %s", p01b == p01));
    }
}
  • linha 14: cria o contexto Spring. Todos os singletons definidos no arquivo [config-01.xml] são então instanciados;
  • linha 16: solicita uma referência ao singleton identificado por [personne_01], do tipo [Personne]. Esse segundo parâmetro é opcional, mas, nesse caso, recebe-se uma referência a um tipo [Object], referência essa que deve então ser convertida para o tipo [Personne];
  • linha 19: não se utiliza o nome do bean, mas apenas seu tipo, pois há apenas um singleton do tipo [Appartement];
  • linha 18: utilizou-se tanto o identificador quanto o tipo do singleton desejado. O identificador é supérfluo, uma vez que existe apenas um singleton do tipo [new ArrayList<Personne>().getClass()];
  • linhas 32-33: mostram que, se solicitarmos o mesmo singleton várias vezes, sempre obtemos a mesma referência, comprovando assim que se trata efetivamente de um singleton. É importante compreender esse ponto;

Nota: uma classe executável pode ser gerada da seguinte maneira [1-6]:

  • é ao marcar [6] que a classe gerada passará a conter um método estático [main], o que a tornará executável;

2.2.6. As dependências do projeto

 
  • Dependências do Spring: [spring-core, spring-beans, spring-context, spring-expression, commons-logging];

As dependências são adicionadas ao projeto da seguinte maneira:

  • em [1]: clique com o botão direito do mouse no projeto / [Build Path] / [Configure Build Path];
  • em [2]: [Add JARs] se os arquivos JARs a serem adicionados estiverem em uma pasta do projeto. Caso contrário, [Add External JARs];
  • em [3], selecione os JARs a serem adicionados ao ClassPath do projeto (eles estão aqui na pasta [lib] dentro do projeto);

Definição: o [ClassPath] de um projeto é o conjunto de pastas exploradas pelo JVM (Java Virtual Machine) que executa o projeto, para localizar uma classe referenciada por ele. Para um projeto Eclipse, o [ClassPath] é composto pelos seguintes elementos:

  • a pasta [bin] do projeto;
  • os elementos do [Build Path] do projeto;

A pasta [bin] é a pasta gerada pela compilação da pasta [src]. Portanto, tudo o que estiver na pasta [src] passa automaticamente a fazer parte da pasta [ClassPath] (mesmo que não seja um arquivo .java). Portanto, no projeto anterior, o arquivo de configuração do Spring [config-01.xml], que está na pasta [src], fará parte da pasta [Classpath] do projeto durante a execução.

2.2.7. Os resultados

févr. 21, 2014 1:16:23 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
Infos: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@3ac67f69: startup date [Fri Feb 21 13:16:23 CET 2014]; root of context hierarchy
févr. 21, 2014 1:16:23 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
Infos: Loading XML bean definitions from class path resource [config-01.xml]
personnes--------
Personne[paul, dubois,34]
Personne[micheline, martin,18]
club--------
Personne[paul, dubois,34]
Personne[micheline, martin,18]
appartement--------
Appartement[Personne[paul, dubois,34], 100]
beans [p01,p01b] identiques ? true

2.3. Exemple-02

2.3.1. O projeto Eclipse

 

2.3.2. A classe de configuração do Spring


package istia.st.spring.core;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {

    @Bean
    public Personne personne_01() {
        return new Personne("Paul", "Dubois", 34);
    }

    @Bean
    public Personne personne_02() {
        return new Personne("Martin", "Micheline", 18);
    }

    @Bean
    public List<Personne> club(Personne personne_01, Personne personne_02) {
        List<Personne> personnes = new ArrayList<Personne>();
        personnes.add(personne_01);
        personnes.add(personne_02);
        return personnes;
    }

    @Bean
    public Appartement appartement(Personne personne_01) {
        Appartement appartement = new Appartement();
        appartement.setSurface(200);
        appartement.setPropriétaire(personne_01);
        return appartement;
    }
}
  • linha 9: a anotação [@Configuration] é uma anotação do Spring. Ela indica que a classe anotada define singletons. Estes são definidos por meio da anotação [@Bean]. O Spring executará todos os métodos anotados com [@Bean]. Esses métodos criam os singletons da aplicação;
  • linhas 12-15: definem um singleton identificado por [personne_01], ou seja, o nome do método.
  • linha 23: os parâmetros [personne_01, personne_02] têm os nomes dos singletons. O Spring os inicializará automaticamente com as referências desses singletons. Isso é conhecido como injeção de parâmetros;

Essa forma de configurar os singletons é mais explícita do que aquela que utiliza o arquivo XML. Na verdade, estamos reproduzindo nós mesmos o que o Spring fazia implicitamente a partir do arquivo XML.

2.3.3. A classe executável

 

package istia.st.spring.core;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo02 {

    @SuppressWarnings({ "unchecked", "resource" })
    public static void main(String[] args) {
        // recuperação do contexto Spring
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
        // recuperamos os beans
        Personne p01 = ctx.getBean("personne_01", Personne.class);
        Personne p02 = ctx.getBean("personne_02", Personne.class);
        List<Personne> club = ctx.getBean("club", new ArrayList<Personne>().getClass());
        Appartement appart01 = ctx.getBean(Appartement.class);
        // eles são exibidos
        System.out.println("personnes--------");
        System.out.println(p01);
        System.out.println(p02);
        System.out.println("club--------");
        for (Personne p : club) {
            System.out.println(p);
        }
        System.out.println("appartement--------");
        System.out.println(appart01);
        // os beans recuperados são singletons
        // é possível solicitá-los várias vezes; sempre se recupera o mesmo bean
        Personne p01b = ctx.getBean("personne_01", Personne.class);
        System.out.println(String.format("beans [p01,p01b] identiques ? %s", p01b == p01));
    }
}
  • a linha 13 provoca a instanciação de todos os beans definidos na classe [Config];
  • o restante do código permanece inalterado;

2.3.4. As dependências do projeto

As dependências são definidas pelo seguinte arquivo [pom.xml]:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-02</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-core-02</name>
    <description>Introduction à Spring</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.7</java.version>
    </properties>

    <dependencies>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.1.3.RELEASE</version>
        </dependency>
    </dependencies>
    <!-- plug-ins -->
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>

O gerenciamento manual das dependências de um projeto torna-se um desafio quando se utilizam bibliotecas Java cujas dependências desconhecemos. Assim, o framework [Hibernate], que gerencia o acesso a bancos de dados, possui dezenas de dependências. O projeto [Maven] resolve esse problema. Basta nomear a dependência necessária. Ela é procurada automaticamente em repositórios Maven espalhados pela internet. Se a dependência solicitada tiver, por sua vez, outras dependências, estas também serão baixadas automaticamente. Essas dependências baixadas são armazenadas em um repositório local no computador. Se, posteriormente, outro aplicativo precisar da mesma dependência, ela não será baixada novamente, mas sim procurada no repositório local. Uma dependência é caracterizada pelos seguintes elementos:

  • linha 17: uma tag <dependency>;
  • linha 18: um atributo [groupId] que geralmente identifica a empresa que criou a dependência;
  • linha 19: um atributo [artifactId] que identifica a dependência;
  • linha 20: um atributo [version] que identifica a versão desejada;

A geração do projeto produzirá, por sua vez, um componente Maven definido pelas linhas 4 a 8:

  • linhas 4 a 6: os atributos [ groupId, artifactId,version] que acabamos de descrever;
  • linhas 7-8: são atributos opcionais;

Voltaremos a abordar o papel das linhas 24 a 40 um pouco mais adiante. Para transformar um projeto Eclipse comum em um projeto Maven, é preciso fazer duas coisas:

  • criar o arquivo [pom.xml] mencionado anteriormente;
  • declarar que o projeto agora é um projeto Maven [1-4]:

O ícone de um projeto Maven é um M [4]. O S indica que o projeto contém elementos Spring. Não é recomendável converter (como acabamos de fazer) um projeto Eclipse em um projeto Maven, pois, nesse caso, o projeto não possui a estrutura esperada para um projeto Maven, o que pode, às vezes, causar problemas inesperados.

2.3.5. Geração do artefato Maven do projeto

Chamamos de artefato Maven do projeto o elemento definido pelas linhas 4 a 6 do arquivo [pom.xml]:


    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-02</artifactId>
<version>0.0.1-SNAPSHOT</version>

Para gerar esse artefato, é necessário que as seguintes linhas 3 a 7 estejam presentes no arquivo [pom.xml]:


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
</build>

Elas definem o plugin do Maven capaz de gerar o artefato do projeto. Em seguida, procede-se da seguinte maneira:

O artefato assim gerado vai para o repositório local do Maven. A localização deste pode ser encontrada na configuração do Eclipse:

 

É então possível verificar se o artefato do Maven foi instalado corretamente:

 

A partir de agora, outro projeto Maven local poderá utilizar esse arquivo.

2.4. Exemple-03

2.4.1. O projeto Eclipse

Desta vez, criamos um projeto Maven [1-8]:

  • em [3b]: indique uma pasta vazia onde o projeto será gerado;
  • em [4]: o identificador do grupo Maven ao qual o projeto pertencerá;
  • em [5]: o nome do artefato Maven produzido:
  • em [6]: sua versão;
  • em [7]: seu modo de empacotamento (também existem war, ear, apk, etc.);
  • em [8]: o projeto assim criado;

Um projeto Maven possui, por padrão, uma estrutura de diretórios específica:

  • [src / main / java]: os códigos-fonte do projeto. Os arquivos compilados a partir desses códigos-fonte serão colocados na pasta [target/classes] do projeto;
  • [src / main / resources]: os recursos que devem estar no Classpath do projeto, sem necessariamente serem códigos-fonte Java. Eles serão copiados tal como estão para a pasta [target/classes] do projeto;
  • [src / test / java]: os códigos-fonte dos testes do projeto. Os arquivos compilados a partir desses códigos-fonte serão colocados na pasta [target/test-classes] do projeto. Esses elementos não são incluídos no arquivo Maven do projeto;
  • [src / test / resources]: os recursos que devem estar no Classpath do projeto para os testes, sem necessariamente serem códigos-fonte Java. Eles serão copiados tal como estão para a pasta [target/test-classes] do projeto;

Completamos o projeto da seguinte maneira:

 

2.4.2. A configuração do Maven

Um arquivo [pom.xml] é gerado por padrão. Nós o modificamos da seguinte maneira:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-03</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-core-03</name>
    <description>Introduction à Spring</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <!-- projeto pai do Maven -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <dependencies>
        <!-- Contexto Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <!-- logs -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>

    </dependencies>

    <!-- plug-ins -->
    <build>
        <plugins>
            <!-- para a geração do arquivo do projeto com suas dependências -->
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <!-- para a instalação do artefato do projeto no repositório local do Maven -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • linha 11: o projeto está codificado como UTF-8;
  • linha 12: utilizamos um JDK 1.8 para compilar o projeto;
  • linhas 16-20: para projetos que utilizam as bibliotecas Spring, é prático usar um projeto pai do Maven chamado [spring-boot-starter-parent]. Esse projeto define as versões das diferentes bibliotecas Spring, bem como as de suas dependências. Isso permite que não seja mais necessário defini-las na definição de dependências. Assim, nas linhas 24-27, não se especifica a versão desejada do [spring-context]. Será a versão definida pelo projeto pai [spring-boot-starter-parent]. Essa técnica permite não se preocupar com possíveis incompatibilidades de versões entre dependências. As versões definidas pelo projeto pai são compatíveis entre si;
  • linhas 29-32: o Spring grava uma quantidade significativa de informações no console por meio de uma biblioteca de logs. Essa biblioteca é importada aqui;
  • linhas 40-47: um plugin do Maven sobre o qual voltaremos a falar;
  • linhas 50-52: o plugin de geração do artefato Maven do projeto;

2.4.3. A classe de configuração do Spring

  

A classe [Config] é a seguinte:


package istia.st.spring.core.config;

import istia.st.spring.core.entities.Personne;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan({ "spring.core.entities" })
public class Config {

    @Bean
    public Personne personne_01() {
        return new Personne("Paul", "Dubois", 34);
    }

    @Bean
    public Personne personne_02() {
        return new Personne("Martin", "Micheline", 18);
    }

    @Bean
    public List<Personne> club(Personne personne_01, Personne personne_02) {
        List<Personne> personnes = new ArrayList<Personne>();
        personnes.add(personne_01);
        personnes.add(personne_02);
        return personnes;
    }

    @Bean
    public int mySurface() {
        return 200;
    }
}
  • Aqui encontramos um código já comentado com duas novidades:
    • linha 13: indica que há outros beans a serem instanciados no pacote [spring.core.entities],
    • linhas 34-37: um bean [mySurface];

2.4.4. A classe [Appartement]

 

package spring.core.entities;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class Appartement {

    // campos injetados pelo Spring
    @Autowired
    @Qualifier("personne_01")
    private Personne propriétaire;

    @Autowired
    @Qualifier("mySurface")
    private int surface;

    // getters e setters
    public Personne getPropriétaire() {
        return propriétaire;
    }

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

    public int getSurface() {
        return surface;
    }

    public void setSurface(int surface) {
        this.surface = surface;
    }

    // toString
    public String toString() {
        return String.format("Appartement[%s, %s]", propriétaire, surface);
    }

}
  • linha 7: a anotação [@Component] indica ao Spring que a classe é um singleton que o framework deve instanciar e gerenciar. É porque, na classe [Config], escrevemos [@ComponentScan({ "istia.st.spring.core.entities" })] que esse singleton será encontrado;
  • linha 11: solicita ao Spring que injete no campo a referência de um dos singletons. Essa referência pode ser definida de duas maneiras:
    • por seu identificador (linhas 12, 16),
    • pelo seu tipo, caso exista apenas um singleton com esse tipo;

2.4.5. Execução do projeto

A execução do projeto gera o seguinte resultado no console:

17:32:39.797 [main] DEBUG o.s.core.env.StandardEnvironment - Adding [systemProperties] PropertySource with lowest search precedence
....
17:32:40.134 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'appartement'
personnes--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
club--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
appartement--------
Appartement[Personne[Dubois, Paul,34], 200]
17:32:40.135 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'personne_01'
beans [p01,p01b] identiques ? true
  • linhas 1-3: o Spring gera um grande número de logs, várias dezenas de linhas. Esses logs podem ser muito úteis para depurar um projeto que não está funcionando. Quando o projeto está funcionando, é possível reduzir os logs da seguinte maneira:
  

Na pasta [src / main / resources], cria-se o seguinte arquivo [logback.xml]:


<configuration> 

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
    <!-- os codificadores recebem, por padrão, o tipo
         ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- controle do nível dos logs -->
  <root level="info"> <!-- informação, depuração, aviso -->
    <appender-ref ref="STDOUT" />
  </root>
</configuration>
  • na linha 12, define-se o nível dos logs. [debug] é um nível muito detalhado, [info] é bem menos detalhado;

Aqui estão os resultados com [level=info]:

17:39:58.580 [main] INFO  o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7cf10a6f: startup date [Tue Apr 07 17:39:58 CEST 2015]; root of context hierarchy
personnes--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
club--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
appartement--------
Appartement[Personne[Dubois, Paul,34], 200]
beans [p01,p01b] identiques ? true

Agora há apenas uma linha de logs.

2.4.6. Geração do arquivo do projeto com suas dependências

O arquivo criado no projeto anterior também pode ser usado por um projeto Eclipse que não seja Maven. Alguns projetos utilizam muitas bibliotecas e pode ser complicado não esquecer nenhuma delas. É aí que o Maven faz maravilhas, pois basta nomear a dependência de nível mais alto para que as outras, de nível inferior, sejam automaticamente adicionadas ao Classpath do projeto. Quando um projeto Eclipse que não usa o Maven precisa utilizar os arquivos de um projeto Maven, é possível gerar o artefato deste último com todas as suas dependências (o que não era o caso no projeto anterior). Para essa geração, é necessário que as linhas 3 a 10 a seguir estejam presentes no arquivo [pom.xml]:


    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
</build>

Elas definem o plugin do Maven capaz de gerar o artefato do projeto com suas dependências. Em seguida, procede-se da seguinte maneira [1-6]:

  • [4-6] representam uma configuração de execução do Maven;
  • em [4], insira um nome qualquer;
  • em [5], indique a pasta do projeto;
  • em [6], insira os objetivos do Maven (goals):
    • [clean]: a pasta [target] do projeto é excluída;
    • [compile]: o projeto é compilado. Os resultados da compilação são colocados em uma pasta [target] regenerada;
    • [assembly:single]: as classes do projeto e de suas dependências são colocadas em um único arquivo jar na pasta [target];

Após a execução, obtém-se o seguinte resultado:

Um arquivo JAR é um arquivo compactado que, portanto, pode ser aberto com um programa de descompactação. Após descompactar o arquivo anterior, obtém-se a seguinte estrutura de diretórios:

  • em [8], as classes das dependências do projeto;
  • em [9], as classes do próprio projeto;

2.5. Exemple-04

2.5.1. Objetivo

Este exemplo retoma um dos apresentados no documento [Introduction à Spring IoC], no qual se demonstra a contribuição do Spring para a configuração de arquiteturas multicamadas. No documento original, o exemplo é tratado com uma configuração do Spring feita por meio de um arquivo XML. Aqui, abordamos o exemplo com uma configuração por meio de classes Java e anotações.

Queremos, neste caso, configurar um projeto Spring para a seguinte arquitetura:

Cada camada apresenta uma interface implementada por duas classes. Queremos mostrar que, graças ao Spring, é possível alterar a implementação de uma camada sem causar nenhum impacto no código das outras camadas.

2.5.2. O projeto no Eclipse

2.5.2.1. Génération

Criamos um novo tipo de projeto:

  • em [4], insira o nome do projeto Eclipse;
  • em [5], selecione um projeto Maven;
  • em [6], escolha uma versão do Java >=1.7;
  • em [7], selecione a versão do Spring Boot sugerida;
  • as informações em [8-11] são informações do Maven;
  • em [12], é possível selecionar uma ou mais das dependências sugeridas. Isso fará com que um certo número de dependências seja integrado ao arquivo [pom.xml] do Maven;
  • no [13], indicar uma pasta existente e vazia para abrigar o projeto;
  • em [14], o projeto gerado. Vamos analisar seus elementos;

O projeto é um projeto Maven configurado pelo seguinte arquivo [pom.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-04</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring-core-04</name>
    <description>Programmation par interfaces</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
        <relativePath/> <!-- busca do pai no repositório -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>demo.SpringCore04Application</start-class>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  • linhas 6-12: reproduzem as informações inseridas no assistente de criação do projeto;
  • linhas 14-19: o projeto Maven pai, que define várias bibliotecas com suas versões. Se alguma delas for uma dependência do projeto, ela é mencionada no arquivo [pom.xml] sem indicar sua versão;
  • linha 23: essa linha só é necessária se houver a intenção de gerar um arquivo executável do projeto. Caso contrário, ela não é utilizada;
  • linhas 28-31: a dependência mínima de um projeto Spring Boot. Vale lembrar que não selecionamos nenhuma dependência na lista de seleção;
  • linhas 33-37: a dependência necessária para gerenciar testes unitários JUnit e [http://junit.org/] integrados ao Spring. A linha 36 indica que a dependência é necessária apenas para os testes. Consequentemente, ela não será incluída no arquivo do projeto;
  • linhas 42-45: o plugin que permite gerar o artefato Maven do projeto;

A lista de dependências fornecida por este arquivo é a seguinte: [1]:

Veremos que elas são suficientes para o que queremos fazer aqui.

2.5.2.2. A classe executável

  

A classe executável [SpringCore04Application] [[2] é a seguinte:


package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringCore04Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringCore04Application.class, args);
    }
}
  • na linha 6, a anotação [@SpringBootApplication] é um atalho para as três anotações [@Configuration, @EnableAutoConfiguration, @ComponentScan], o que significa:
    • que a classe [SpringCore04Application] é uma classe de configuração do Spring;
    • que o Spring Boot é solicitado a realizar configurações a partir das classes que encontrará no Classpath do projeto, ou seja, neste caso, nas dependências do Maven;
    • que examine a pasta atual (aquela da classe [SpringCore04Application]) para localizar eventuais outros componentes do Spring;
  • linha 10: o método estático [SpringApplication.run] é executado. Seu primeiro parâmetro é uma classe de configuração do Spring, neste caso a classe [SpringCore04Application]. Seu segundo parâmetro é, neste caso, a lista de argumentos passados ao método [main] (linha 9). O método estático [SpringApplication.run] tem a função de criar o contexto Spring, ou seja, criar os diversos beans encontrados nas classes de configuração ou nas pastas exploradas pela anotação [@ComponentScan]. O método [main], neste caso, não faz mais nada. Para dar um pouco mais de substância a ele, vamos transformá-lo da seguinte maneira:

package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class SpringCore04Application {

    public static void main(String[] args) {
        // instanciação do contexto Spring
        ConfigurableApplicationContext context = SpringApplication.run(SpringCore04Application.class, args);
        // exibição do contexto
        System.out.println("---------------- Liste des beans Spring");
        for (String beanName : context.getBeanDefinitionNames()) {
            System.out.println(beanName);
        }
        // fechamento do contexto
        context.close();
    }
}
  • linha 12: o método estático [SpringApplication.run] retorna o contexto Spring que ele construiu;
  • linhas 15-17: exibimos o nome de todos os beans desse contexto;

É possível executar a aplicação da seguinte maneira: [1-3]. O método habitual [Run As Java Application] também é válido.

Obtém-se o seguinte resultado:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.3.RELEASE)

2015-04-08 11:18:38.254  INFO 4796 --- [           main] demo.SpringCore04Application             : Starting SpringCore04Application on Gportpers3 with PID 4796 (D:\data\istia-1415\polys\istia\dvp-spring-database\codes\original\intro-spring-core\spring-core-04\target\classes started by ST in D:\data\istia-1415\polys\istia\dvp-spring-database\codes\original\intro-spring-core\spring-core-04)
2015-04-08 11:18:38.295  INFO 4796 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@64485a47: startup date [Wed Apr 08 11:18:38 CEST 2015]; root of context hierarchy
2015-04-08 11:18:38.776  INFO 4796 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2015-04-08 11:18:38.788  INFO 4796 --- [           main] demo.SpringCore04Application             : Started SpringCore04Application in 0.773 seconds (JVM running for 1.335)
---------------- Liste des beans Spring
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
springCore04Application
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor.enhancedConfigurationProcessor
org.springframework.boot.autoconfigure.AutoConfigurationPackages
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.condition.BeanTypeRegistry
propertySourcesPlaceholderConfigurer
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
mbeanExporter
objectNamingStrategy
mbeanServer
2015-04-08 11:18:38.789  INFO 4796 --- [           main] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@64485a47: startup date [Wed Apr 08 11:18:38 CEST 2015]; root of context hierarchy
2015-04-08 11:18:38.790  INFO 4796 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown
  • linhas 14-28: os beans do contexto Spring. Não sabemos qual é a função deles. Encontramos o bean [springCore04Application] na linha 18, que, devido à sua anotação [@SpringBootApplication], torna-se automaticamente um bean Spring;
  • as demais linhas são logs do Spring de nível [INFO]. Como já vimos, esses logs podem ser controlados pelo arquivo [logback.xml], localizado no Classpath do projeto:
  

<configuration> 

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
    <!-- os codificadores recebem, por padrão, o tipo
         ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- controle do nível dos logs -->
  <root level="warn"> <!-- info, debug, warn -->
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

Se, na linha 12 acima, substituirmos o nível [info] pelo nível [warn], obtemos o seguinte resultado:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.3.RELEASE)

---------------- Liste des beans Spring
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
springCore04Application
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor.enhancedConfigurationProcessor
org.springframework.boot.autoconfigure.AutoConfigurationPackages
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.condition.BeanTypeRegistry
propertySourcesPlaceholderConfigurer
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
mbeanExporter
objectNamingStrategy
mbeanServer

Os logs desapareceram. Aparecem apenas as mensagens do nível [warn] e, neste caso, não houve nenhuma.

2.5.3. Implementação das diferentes camadas da arquitetura

Vamos agora implementar as três camadas da arquitetura acima:

  

A camada [DAO] é implementada pelo pacote [spring.core.dao]. Ela apresenta a seguinte interface [IDao]:


package spring.core.dao;

public interface IDao {

    public int doSomethingInDaoLayer(int a, int b);
}

Essa interface possui duas implementações: [Dao1] e [Dao2]:


package spring.core.dao;

public class Dao1 implements IDao {

    public int doSomethingInDaoLayer(int a, int b) {
        return a+b;
    }

}

package spring.core.dao;

public class Dao2 implements IDao {

    public int doSomethingInDaoLayer(int a, int b) {
        return a-b;
    }

}

A camada [métier] é implementada pelo pacote [spring.core.metier]. Ela apresenta a seguinte interface [IMetier]:


package spring.core.metier;

public interface IMetier {

    public int doSomethingInMetierLayer(int a, int b);
}

Essa interface possui duas implementações: [Metier1] e [Metier2]:


package spring.core.metier;

import spring.core.dao.IDao;

public class Metier1 implements IMetier {

    private IDao dao;

    public int doSomethingInMetierLayer(int a, int b) {
        a++;
        b++;
        return dao.doSomethingInDaoLayer(a, b);
    }

    public void setDao(IDao dao) {
        this.dao = dao;
    }
}

package spring.core.metier;

import spring.core.dao.IDao;

public class Metier2 implements IMetier {

    private IDao dao;

    public int doSomethingInMetierLayer(int a, int b) {
        a--;
        b++;
        return dao.doSomethingInDaoLayer(a, b);
    }

    public void setDao(IDao dao) {
        this.dao = dao;
    }


}

A camada [UI] é implementada pelo pacote [spring.core.ui]. Ela apresenta a seguinte interface [IUi]:


package spring.core.ui;

public interface IUi {

    public int doSomethingInUiLayer(int a, int b);
}

Essa interface possui duas implementações: [Ui1] e [Ui2]:


package spring.core.ui;

import spring.core.metier.IMetier;

public class Ui1 implements IUi {

    private IMetier metier;
    
    public int doSomethingInUiLayer(int a, int b) {
        a++;
        b++;
        return metier.doSomethingInMetierLayer(a, b);
    }

    public void setMetier(IMetier metier) {
        this.metier = metier;
    }

}

package spring.core.ui;

import spring.core.metier.IMetier;

public class Ui2 implements IUi {

    private IMetier metier;

    public int doSomethingInUiLayer(int a, int b) {
        a--;
        b++;
        return metier.doSomethingInMetierLayer(a, b);
    }

    public void setMetier(IMetier metier) {
        this.metier = metier;
    }

}

2.5.4. Configuração do projeto Spring

  

A classe de configuração [Config] é a seguinte:


package spring.core.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import spring.core.dao.Dao1;
import spring.core.dao.Dao2;
import spring.core.dao.IDao;
import spring.core.metier.IMetier;
import spring.core.metier.Metier1;
import spring.core.metier.Metier2;
import spring.core.ui.IUi;
import spring.core.ui.Ui1;
import spring.core.ui.Ui2;

@Configuration
public class Config {

    // -------------- implementação [Ui1, Metier1, Dao1]
    @Bean
    public IDao dao1() {
        return new Dao1();
    }

    @Bean
    public IMetier metier1(IDao dao1) {
        Metier1 metier = new Metier1();
        metier.setDao(dao1);
        return metier;
    }

    @Bean
    public IUi ui1(IMetier metier1) {
        Ui1 ui = new Ui1();
        ui.setMetier(metier1);
        return ui;
    }

    // -------------- implementação [Ui2, Metier2, Dao2]
    @Bean
    public IDao dao2() {
        return new Dao2();
    }

    @Bean
    public IMetier metier2(IDao dao2) {
        Metier2 metier = new Metier2();
        metier.setDao(dao2);
        return metier;
    }

    @Bean
    public IUi ui2(IMetier metier2) {
        Ui2 ui = new Ui2();
        ui.setMetier(metier2);
        return ui;
    }
}
  • linhas 20-23: o bean denominado [dao1] (nome do método) é uma instância da classe [Dao1] (linha 22), considerada uma implementação da interface [IDao] (linha 21). O bean [dao1] é, portanto, visto como uma instância de interface (a terminologia está incorreta, mas dá para entender) e não como uma instância de classe. Esse é um ponto importante a ser compreendido. Todos os outros beans também serão instâncias de interfaces;
  • linhas 25-30: uma instância da interface [IMetier] implementada pela classe [Metier1];
  • linhas 32-37: uma instância da interface [IUi] implementada pela classe [Ui1];
  • linhas 20-37: implementam as camadas [UI, Metier, DAO] com instâncias de [Ui1, Metier1, Dao1];
  • linhas 40-57: implementam as camadas [UI, Metier, DAO] com instâncias de [Ui2, Metier2, Dao2];

2.5.5. Teste unitário [JUnitTest]

  

A classe [JUnitTest] está localizada no ramo [src / test / java] do projeto Maven. Os elementos desse ramo não são incluídos no arquivo final do projeto. Seu código é o seguinte:


package spring.core.tests;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import spring.core.config.Config;
import spring.core.dao.IDao;
import spring.core.metier.IMetier;
import spring.core.ui.IUi;

@SpringApplicationConfiguration(classes = { Config.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTest {
...
}
  • linha 16: a anotação [@SpringApplicationConfiguration] é uma anotação do projeto Spring Boot Test (linha 8). Ela é introduzida pela seguinte dependência do arquivo [pom.xml]:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
</dependency>

Essa anotação tem como parâmetro a lista de classes de configuração a serem utilizadas para construir o contexto Spring necessário para o teste. Aqui, utilizamos a classe de configuração [Config] já apresentada;

  • linha 17: a anotação [@RunWith] é uma anotação JUnit (linha 5). Seu parâmetro é a classe responsável por executar os testes no lugar da classe padrão do framework JUnit. Essa classe é uma classe Spring (linha 9). Ela utilizará as anotações Spring presentes na classe de teste;

A classe completa é a seguinte


...

@SpringApplicationConfiguration(classes = { Config.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTest {

    // camada [UI]
    @Autowired
    @Qualifier("ui1")
    private IUi ui1;

    @Autowired
    @Qualifier("ui2")
    private IUi ui2;

    // camada [métier]
    @Autowired
    @Qualifier("metier1")
    private IMetier metier1;

    @Autowired
    @Qualifier("metier2")
    private IMetier metier2;

    // camada [dao]
    @Autowired
    @Qualifier("dao1")
    private IDao dao1;

    @Autowired
    @Qualifier("dao2")
    private IDao dao2;

    @Test
    public void testDao() {
        Assert.assertEquals(30, dao1.doSomethingInDaoLayer(10, 20));
        Assert.assertEquals(-10, dao2.doSomethingInDaoLayer(10, 20));
    }

    @Test
    public void testMetier() {
        Assert.assertEquals(32, metier1.doSomethingInMetierLayer(10, 20));
        Assert.assertEquals(-12, metier2.doSomethingInMetierLayer(10, 20));
    }

    @Test
    public void testUI() {
        Assert.assertEquals(34, ui1.doSomethingInUiLayer(10, 20));
        Assert.assertEquals(-14, ui2.doSomethingInUiLayer(10, 20));
    }

}
  • linhas 8-10: injetamos (linha 8) o bean denominado (linha 9) [ui1]. Observe-se na linha 10 que injetamos uma instância de interface e não uma instância de classe;
  • linhas 21-32: injetam-se, da mesma forma, os outros beans definidos na classe [Config];
  • linha 34: a anotação [@Test] indica um método a ser executado durante os testes. As outras anotações possíveis são as seguintes:
    • [@BeforeClass]: método a ser executado antes do início dos testes;
    • [@AfterClass]: método a ser executado após a conclusão de todos os testes;
    • [@Before]: método a ser executado antes de cada teste;
    • [@After]: método a ser executado após cada teste;
  • linha 36: verifica-se se a chamada [dao1.doSomethingInDaoLayer(10, 20)] realmente retorna 30. Por convenção, o primeiro parâmetro é o valor esperado e o segundo, o valor real;
  • linha 36: testa a instância [dao1] da interface [IDao];
  • linha 37: testa a instância [dao2] da interface [IDao];
  • linha 42: testa a instância [metier1] da interface [IMetier];
  • linha 43: testa a instância [metier2] da interface [IMetier];
  • linha 48: testa a instância [ui1] da interface [IUi];
  • linha 36: testa a instância [ui2] da interface [IUi];

As asserções que podem ser utilizadas em um método de teste são as seguintes:

  • assertEquals(expressão1, expressão2): verifica se os valores das duas expressões são iguais. São aceitos diversos tipos de expressão (int, String, float, double, boolean, char, short). Se as duas expressões não forem iguais, é lançada uma exceção do tipo [AssertionFailedError ],
  • assertEquals(real1, real2, delta): verifica se dois valores reais são iguais com uma tolerância de delta, ou seja, c.a.d abs(real1 - real2) <= delta. Pode-se escrever, por exemplo, assertEquals(real1, real2, 1E-6) para verificar se dois valores são iguais com uma precisão de 10⁻⁶,
  • assertEquals(mensagem, expressão1, expressão2) e assertEquals(mensagem, real1, real2, delta) são variantes que permitem especificar a mensagem de erro a ser associada à exceção do tipo [AssertionFailedError] lançada quando o método [assertEquals] falha,
  • assertNotNull(Object) e assertNotNull(message, Object): verifica se a referência Object não é igual a null,
  • assertNull(Object) e assertNull(message, Object): verifica se a referência Object é igual a null,
  • assertSame(Object1, Object2) e assertSame(mensagem, Object1, Object2): verifica se as referências Object1 e Object2 apontam para o mesmo objeto,
  • assertNotSame(Object1, Object2) e assertNotSame(mensagem, Object1, Object2): verifica se as referências Object1 e Object2 não apontam para o mesmo objeto;

Para executar o teste, pode-se proceder da seguinte maneira:

Obtém-se o seguinte resultado:

 

Aqui, todos os testes foram bem-sucedidos. O que esse exemplo demonstra, afinal? Ele mostra a flexibilidade proporcionada pelo framework Spring na configuração de uma arquitetura em camadas. É possível decidir usar a implementação [Ui1, Metier1, Dao1] ou [Ui2, Metier2, Dao2] simplesmente por meio da configuração. Assim, no teste JUnit anterior, se mantivermos apenas a injeção dos beans [ui1, metier1, dao1], estaremos trabalhando com a primeira arquitetura. Para mudar de arquitetura, basta alterar os beans injetados. Isso é feito sem alterar o código das camadas que implementam as interfaces. Esse tipo de programação é chamado de programação por interfaces, pois não se utilizam as instâncias das classes que implementam as camadas, mas sim as instâncias de suas interfaces.

2.6. Conclusion

  • O Spring gerencia objetos que são singletons (uma única instância). O Spring também gerencia objetos que são instanciados sempre que se solicita uma instância ao Spring. Esse caso também será apresentado neste documento;
  • esses objetos podem ser declarados de diversas maneiras, que podem ser combinadas:
    • em um arquivo XML,
    • em uma classe Java anotada com [@Configuration],
    • com qualquer classe Java anotada com [@Component, @Service, ...];
  • um objeto Spring pode ser injetado em outro objeto Spring com a anotação [@Autowired]. Nesse caso, fala-se em injeção de dependências (DI: Dependency Injection);
  • O Spring se mostra muito prático para configurar arquiteturas em camadas com o uso conjunto do paradigma de programação por interfaces;