5. [Cours]: Introdução ao framework Spring
Palavras-chave: arquitetura multicamadas, Spring, injeção de dependências.
O Spring surgiu em 2004, inicialmente como um contentor 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 contentor de objetos. Aqui ficam alguns pontos de referência:
- Uma aplicação tem várias classes e algumas delas partilham objetos que têm de ser únicos (singletons). O Spring cria e gere esses singletons;
- o Spring coloca esses singletons numa estrutura denominada contexto;
- as classes têm acesso aos singletons da aplicação, solicitando-os ao Spring através do seu nome, do seu tipo ou de ambos;
- O Spring cria os singletons e gere as suas eventuais dependências: um singleton pode, de facto, ter referências a um ou mais outros singletons. Quando o Spring cria um singleton, cria também as suas dependências;
- quando uma aplicação baseada no Spring é iniciada, 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 a utilização 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 as 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 seguintes:
- a aplicação obtém uma referência à camada através do seu nome. O Spring fornece-lhe 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 formas, que podem ser combinadas:
- num ficheiro XML,
- numa classe especial de configuração;
- com qualquer classe, utilizando anotações;
Apresentamos a seguir três exemplos de configuração:
- [exemple-01]: configuração centralizada num único ficheiro XML;
- [exemple-02]: configuração centralizada numa ú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. Este é o exemplo mais importante. Será este que será constantemente utilizado para configurar as arquiteturas do documento.
Estes quatro exemplos estabelecem as bases para o que se segue:
- configuração Spring e injeção de dependências;
- utilização do Maven para gerir as dependências de um projeto;
- utilização do JUnit para testar os projetos;
5.1. Support
![]() | ![]() |
A pasta [support / chap-05] contém os projetos Eclipse deste capítulo.
5.2. Exemple-01
5.2.1. O projeto Eclipse
![]() |
5.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;
}
}
Nota: os getters e setters podem ser gerados automaticamente da seguinte forma [1-2]:
![]() |
5.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);
}
}
Nota: esta classe não possui um construtor explícito. Neste caso, existe sempre, por predefinição, o construtor sem parâmetros que não realiza nenhuma ação. Quando se criam construtores, este construtor por predefinição deixa de existir implicitamente. É, portanto, necessário defini-lo explicitamente:
5.2.4. O ficheiro 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 e 27: os singletons são definidos dentro de uma baliza <beans>;
- linhas 6-10: cada singleton é definido por uma baliza <bean>;
- linha 6: [id] é o identificador do singleton. [class] é o nome completo da classe a instanciar;
- linhas 7-9: os três valores a passar ao construtor da classe [Personne];
- linhas 12-16: a classe [Personne] é primeiro criada com o seu construtor por predefinição [new Personne()]. Em seguida, para cada baliza [property], é utilizado um setter da classe. Por exemplo, na linha 13, o método [setNom("martin")] será executado. Por isso, é necessário que o método [setNom] exista. Este é um ponto importante a ter em conta;
- linhas 18-21: a baliza <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 se denomina injeção de dependências. Podem ser utilizados dois atributos para inicializar o campo de um singleton:
- [value]: para atribuir ao campo um valor primitivo (cadeia de caracteres, número, data, ...),
- [ref]: para atribuir ao campo a referência de um objeto Spring;
Nota: o ficheiro de configuração do Spring pode ser gerado da seguinte forma [1-4]:
![]() |
5.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");
// recuperam-se 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);
// exibem-se
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
// podem ser solicitados várias vezes, obtém-se sempre 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 ficheiro [config-01.xml] são então instanciados;
- linha 16: solicita uma referência ao singleton identificado por [personne_01], do tipo [Personne]. Este segundo parâmetro é opcional, mas, nesse caso, recebe-se uma referência a um tipo [Object], referência essa que tem de ser convertida para o tipo [Personne];
- linha 19: não se utiliza o nome do bean, mas apenas o seu tipo, uma vez que existe apenas um singleton do tipo [Appartement];
- linha 18: utilizou-se tanto o identificador como o tipo do singleton pretendido. O identificador é supérfluo, uma vez que existe apenas um singleton do tipo [new ArrayList<Personne>().getClass()];
- linhas 32-33: mostram que, se solicitarmos várias vezes o mesmo singleton, obtemos sempre a mesma referência, demonstrando assim que se trata efetivamente de um singleton. É importante compreender este ponto;
Nota: pode ser gerada uma classe executável da seguinte forma [1-6]:
![]() |
![]() |
- é ao marcar [6] que a classe gerada irá conter um método estático [main], o que a tornará executável;
5.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 forma:
![]() |
- em [1]: clique com o botão direito do rato no projeto / [Build Path] / [Configure Build Path];
![]() |
- em [2]: [Add JARs] se os ficheiros JARs a adicionar estiverem numa pasta do projeto. Caso contrário, [Add External JARs];
![]() |
- em [3], selecione os JARs a adicionar ao ClassPath do projeto (neste caso, encontram-se 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 encontrar uma classe referenciada por este. Num 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 resultante da compilação da pasta [src]. Assim, tudo o que estiver na pasta [src] faz automaticamente parte da pasta [ClassPath] (mesmo que não seja um ficheiro .java). Assim, no projeto anterior, o ficheiro de configuração do Spring [config-01.xml], que se encontra na pasta [src], fará parte da pasta [Classpath] do projeto durante a execução.
5.2.7. Os resultados
5.3. Exemple-02
5.3.1. O projeto Eclipse
![]() |
5.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. Indica que a classe anotada define singletons. Estes são definidos através da anotação [@Bean]. O Spring irá executar todos os métodos anotados com [@Bean]. Estes 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 irá inicializá-los automaticamente com as referências desses singletons. Fala-se de injeção de parâmetros;
Esta forma de configurar os singletons é mais explícita do que a que utiliza o ficheiro XML. Na verdade, estamos a reproduzir nós próprios o que o Spring fazia implicitamente a partir do ficheiro XML.
5.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);
// recuperam-se 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);
// 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, obtém-se sempre 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 resto do código permanece inalterado;
5.3.4. As dependências do projeto
![]() | ![]() |
As dependências são definidas pelo seguinte ficheiro [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>
<!-- plugins -->
<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>
A gestão manual das dependências de um projeto torna-se um quebra-cabeças quando se utilizam bibliotecas Java cujas dependências desconhecemos. Assim, o framework [Hibernate], que gere o acesso às bases de dados, tem dezenas de dependências. O projeto [Maven] resolve este problema. Basta indicar a dependência necessária. Esta é procurada automaticamente em repositórios Maven espalhados pela Internet. Se a dependência solicitada tiver, por sua vez, outras dependências, estas também são descarregadas automaticamente. Estas dependências descarregadas são armazenadas num repositório local no computador. Se, mais tarde, outra aplicação precisar da mesma dependência, esta não será descarregada, mas sim procurada no repositório local. Uma dependência é caracterizada pelos seguintes elementos:
- linha 17: uma baliza <dependency>;
- linha 18: um atributo [groupId] que identifica, geralmente, 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 pretendida;
A geração do projeto irá, por sua vez, produzir um componente Maven definido pelas linhas 4-8:
- linhas 4-6: os atributos [ groupId, artifactId,version] que acabámos de descrever;
- linhas 7-8: são atributos opcionais;
Voltaremos mais adiante ao papel das linhas 24 a 40. Para transformar um projeto Eclipse normal num projeto Maven, é necessário fazer duas coisas:
- criar o ficheiro [pom.xml] acima referido;
- declarar que o projeto é agora um projeto Maven [1-4]:
![]() |
O ícone de um projeto Maven tem um M [4]. O S indica que o projeto contém elementos Spring. Não é aconselhável converter (como acabámos de fazer) um projeto Eclipse num projeto Maven, pois, nesse caso, o projeto não tem a estrutura esperada para um projeto Maven, o que pode, por vezes, causar problemas inesperados.
5.3.5. Geração do artefacto Maven do projeto
Chamamos de artefacto Maven do projeto o elemento definido pelas linhas 4 a 6 do ficheiro [pom.xml]:
<groupId>istia.st.spring.core</groupId>
<artifactId>spring-core-02</artifactId>
<version>0.0.1-SNAPSHOT</version>
Para gerar este artefacto, é necessário que as seguintes linhas 3 a 7 estejam presentes no ficheiro [pom.xml]:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
Estas definem o plugin do Maven capaz de gerar o artefacto do projeto. Em seguida, procede-se da seguinte forma:
![]() |
O artefacto assim gerado é colocado no repositório local do Maven. A localização deste pode ser encontrada na configuração do Eclipse:
![]() |
É então possível verificar se o artefacto Maven foi instalado corretamente:
![]() |
A partir de agora, outro projeto Maven local poderá utilizar este arquivo.
5.4. Exemple-03
5.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 artefacto Maven produzido:
- em [6]: a sua versão;
- em [7]: o seu modo de empacotamento (existem também war, ear, apk, ...);
- em [8]: o projeto assim criado;
Um projeto Maven tem, por predefinição, uma estrutura de diretórios específica:
- [src / main / java]: os códigos-fonte do projeto. Os produtos compilados a partir destes códigos-fonte serão colocados na pasta [target/classes] do projeto;
- [src / main / resources]: os recursos que devem constar no Classpath do projeto, sem serem necessariamente código-fonte Java. 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 produtos compilados a partir destes códigos-fonte serão colocados na pasta [target/test-classes] do projeto. Estes 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 serem necessariamente código-fonte Java. Serão copiados tal como estão para a pasta [target/test-classes] do projeto;
Completamos o projeto da seguinte forma:
![]() |
5.4.2. A configuração do Maven
É gerado por predefinição um ficheiro [pom.xml]. Modificamo-lo da seguinte forma:
<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>
<!-- registos -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
<!-- plugins -->
<build>
<plugins>
<!-- para a geração do arquivo do projeto com as suas dependências -->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<!-- para a instalação do artefacto 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 é codificado em UTF-8;
- linha 12: utiliza-se um JDK 1.8 para compilar o projeto;
- linhas 16-20: para projetos que utilizam as bibliotecas Spring, é prático utilizar um projeto pai Maven denominado [spring-boot-starter-parent]. Este define as versões das diferentes bibliotecas Spring, bem como as das suas dependências. Isto permite que já não seja necessário defini-las na definição das dependências. Assim, nas linhas 24-27, não se especifica a versão pretendida do [spring-context]. Será a versão definida pelo projeto pai [spring-boot-starter-parent]. Esta técnica permite não ter de se preocupar com eventuais 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 apresenta uma quantidade significativa de informações na consola através de uma biblioteca de registos. Esta é importada aqui;
- linhas 40-47: um plugin do Maven sobre o qual voltaremos a falar;
- linhas 50-52: o plugin de geração do artefacto Maven do projeto;
5.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;
}
}
- Encontramos aqui um código já comentado com duas novidades:
- linha 13: indica que existem outros beans a instanciar no pacote [spring.core.entities],
- linhas 34-37: um bean [mySurface];
5.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 gerir. É porque, na classe [Config], escrevemos [@ComponentScan({ "istia.st.spring.core.entities" })] que este singleton será encontrado;
- linha 11: solicita ao Spring que injete no campo a referência de um dos singletons. Esta pode ser definida de duas formas:
- pelo seu identificador (linhas 12, 16),
- pelo seu tipo, caso exista apenas um singleton com esse tipo;
5.4.5. Execução do projeto
A execução do projeto apresenta o seguinte resultado na consola:
- linhas 1-3: O Spring gera um número muito elevado de registos, várias dezenas de linhas. Estes registos podem ser muito úteis para depurar um projeto que não está a funcionar. Quando o projeto está a funcionar, é possível reduzir os registos da seguinte forma:
![]() |
Na pasta [src / main / resources], é criado o seguinte ficheiro [logback.xml]:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- aos codificadores é atribuído, por predefiniçã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>
<!-- controlo do nível dos registos -->
<root level="info"> <!-- info, debug, warn -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
- na linha 12, define-se o nível dos registos. O ficheiro [debug] apresenta um nível muito detalhado, enquanto o [info] é muito menos detalhado;
Eis os resultados com o [level=info]:
Agora há apenas uma linha de registos.
5.4.6. Geração do arquivo do projeto com as suas dependências
O arquivo criado no projeto anterior também pode ser utilizado por um projeto Eclipse que não seja Maven. Alguns projetos utilizam numerosas bibliotecas e pode ser complicado não se esquecer de nenhuma. É aqui 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 não Maven precisa de utilizar os arquivos de um projeto Maven, é possível gerar o artefacto deste último com todas as suas dependências (o que não era o caso no projeto anterior). Para esta geração, é necessário que as seguintes linhas 3-10 estejam presentes no ficheiro [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>
Estas definem o plugin do Maven capaz de gerar o artefacto do projeto com as suas dependências. Em seguida, procede-se da seguinte forma [1-6]:
![]() |
![]() |
- [4-6] representam uma configuração de execução do Maven;
- em [4], introduza um nome qualquer;
- em [5], indique a pasta do projeto;
- em [6], introduza os objetivos do Maven (goals):
- [clean]: a pasta [target] do projeto é eliminada;
- [compile]: o projeto é compilado. Os resultados da compilação são colocados numa pasta [target] regenerada;
- [assembly:single]: as classes do projeto e das suas dependências são colocadas num único arquivo jar na pasta [target];
Após a execução, obtém-se o seguinte resultado:
![]() |
Um arquivo jar é um ficheiro compactado que pode, portanto, ser aberto com um programa de descompactação. Depois de 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;
5.5. Exemple-04
5.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 através de um ficheiro XML. Aqui, abordamos o exemplo com uma configuração através de classes Java e anotações.
Pretendemos, neste caso, configurar um projeto Spring para a seguinte arquitetura:
![]() |
Cada camada apresenta uma interface implementada por duas classes. Pretendemos demonstrar que, graças ao Spring, é possível alterar a implementação de uma camada sem qualquer impacto no código das outras camadas.
5.5.2. O projeto Eclipse
5.5.2.1. Génération
Criamos um novo tipo de projeto:
![]() |
![]() |
- em [4], introduza o nome do projeto Eclipse;
- em [5], selecione um projeto Maven;
- em [6], selecione 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 várias das dependências sugeridas. Isto terá como efeito a integração de um determinado número de dependências no ficheiro [pom.xml] do Maven;
![]() |
- no [13], indicar uma pasta existente e vazia para alojar o projeto;
- no [14], o projeto gerado. Vamos analisar os seus elementos;
O projeto é um projeto Maven configurado pelo seguinte ficheiro [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/> <!-- pesquisa 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 introduzidas no assistente de criação do projeto;
- linhas 14-19: o projeto Maven pai, que define várias bibliotecas com as respetivas versões. Se alguma delas for uma dependência do projeto, é mencionada no ficheiro [pom.xml] sem indicar a versão;
- linha 23: esta linha só é utilizada se se pretender gerar um arquivo executável do projeto. Caso contrário, fica sem uso;
- linhas 28-31: a dependência mínima de um projeto Spring Boot. Recorde-se que não selecionámos nenhuma dependência na lista de seleção;
- linhas 33-37: a dependência necessária para gerir testes unitários JUnit [http://junit.org/] integrados com o Spring. A linha 36 indica que a dependência só é necessária para os testes. Consequentemente, não será incluída no arquivo do projeto;
- linhas 42-45: o plugin que permite gerar o artefacto Maven do projeto;
A lista de dependências incluída neste ficheiro é a seguinte: [1]:
![]() |
Veremos que estas são suficientes para o que pretendemos fazer aqui.
5.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:
- que a classe [SpringCore04Application] é uma classe de configuração do Spring;
- que é solicitado ao Spring Boot que efetue configurações a partir das classes que encontrar no Classpath do projeto, ou seja, neste caso, nas dependências Maven;
- que examine a pasta atual (a da classe [SpringCore04Application]) para encontrar eventuais outros componentes Spring;
- linha 10: o método estático [SpringApplication.run] é executado. O seu primeiro parâmetro é uma classe de configuração do Spring, neste caso a classe [SpringCore04Application]. O 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 como função criar o contexto Spring, ou seja, criar os diferentes beans encontrados quer nas classes de configuração, quer nas pastas exploradas pela anotação [@ComponentScan]. O método [main], neste caso, não faz mais nada. Para lhe dar um pouco mais de substância, vamos transformá-lo da seguinte forma:
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);
}
// encerramento do contexto
context.close();
}
}
- linha 12: o método estático [SpringApplication.run] devolve o contexto Spring que construiu;
- linhas 15-17: exibimos o nome de todos os beans desse contexto;
É possível executar a aplicação da seguinte forma: [1-3]. O método habitual [Run As Java Application] também é válido.
![]() |
Obtém-se o seguinte resultado:
- linhas 14-28: os beans do contexto Spring. Não sabemos qual é a sua função. Encontramos o bean [springCore04Application] na linha 18 que, devido à sua anotação [@SpringBootApplication], se torna automaticamente um bean Spring;
- as restantes linhas são registos do Spring de nível [INFO]. Como já vimos, estes registos podem ser controlados pelo ficheiro [logback.xml] colocado no Classpath do projeto:
![]() |
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- Aos codificadores é atribuído, por predefiniçã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>
<!-- controlo do nível dos registos -->
<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:
Os registos desapareceram. Apenas aparecem as mensagens de nível [warn] e, neste caso, não houve nenhuma.
5.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]. Apresenta a seguinte interface [IDao]:
package spring.core.dao;
public interface IDao {
public int doSomethingInDaoLayer(int a, int b);
}
Esta interface tem 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]. Apresenta a seguinte interface [IMetier]:
package spring.core.metier;
public interface IMetier {
public int doSomethingInMetierLayer(int a, int b);
}
Esta interface tem 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]. Apresenta a seguinte interface [IUi]:
package spring.core.ui;
public interface IUi {
public int doSomethingInUiLayer(int a, int b);
}
Esta interface tem 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;
}
}
5.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 como uma implementação da interface [IDao] (linha 21). O bean [dao1] é, portanto, considerado uma instância de interface (a terminologia está incorreta, mas é compreensível) e não uma instância de classe. Este é um ponto importante a compreender. Todos os outros beans serão também 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 [Ui1, Metier1, Dao1];
- linhas 40-57: implementam as camadas [UI, Metier, DAO] com instâncias [Ui2, Metier2, Dao2];
5.5.5. Teste unitário [JUnitTest]
![]() |
A classe [JUnitTest] é colocada no ramo [src / test / java] do projeto Maven. Os elementos deste ramo não são incluídos no arquivo final do projeto. O 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). É introduzida pela seguinte dependência do ficheiro [pom.xml]:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
Esta anotação tem como parâmetro a lista de classes de configuração a utilizar 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). O seu parâmetro é a classe responsável por executar os testes em vez da classe predefinida do framework JUnit. Esta classe é uma classe Spring (linha 9). Ela irá 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: injeta-se (linha 8) o bean denominado (linha 9) [ui1]. Note-se, na linha 10, que se injeta 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 executar antes de iniciar os testes;
- [@AfterClass]: método a executar após a conclusão de todos os testes;
- [@Before]: método a executar antes de cada teste;
- [@After]: método a executar após cada teste;
- linha 36: verifica-se se a chamada [dao1.doSomethingInDaoLayer(10, 20)] devolve efetivamente 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 num 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 aceites vários 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, 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 associar à 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(message, 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 forma:
![]() |
Obtém-se o seguinte resultado:
![]() |
Aqui, todos os testes foram bem-sucedidos. O que é que este exemplo demonstra? Demonstra a flexibilidade proporcionada pelo framework Spring na configuração de uma arquitetura em camadas. É possível optar por utilizar a implementação [Ui1, Metier1, Dao1] ou [Ui2, Metier2, Dao2] simplesmente através da configuração. Assim, no teste JUnit anterior, se mantivermos apenas a injeção dos beans [ui1, metier1, dao1], estamos a trabalhar com a primeira arquitetura. Para mudar de arquitetura, basta alterar os beans injetados. Isto é feito sem alterar o código das camadas que implementam as interfaces. A este tipo de programação chama-se «programação por interfaces», uma vez que não se utilizam as instâncias das classes que implementam as camadas, mas sim as instâncias das respetivas interfaces.
5.6. Conclusion
- O Spring gere objetos que são singletons (uma única instância). O Spring também gere objetos que são instanciados sempre que se solicita uma instância ao Spring. Este caso também será apresentado neste documento;
- estes objetos podem ser declarados de várias formas, que podem ser combinadas:
- num ficheiro XML,
- numa classe Java anotada com [@Configuration],
- com qualquer classe Java anotada com [@Component, @Service, ...];
- um objeto Spring pode ser injetado noutro objeto Spring com a anotação [@Autowired]. Fala-se então de injeção de dependências (DI: Dependency Injection);
- O Spring revela-se muito prático para configurar arquiteturas em camadas com a utilização conjunta do paradigma de programação por interfaces;














































