Usando Maven para gerenciar dependências no Java
O Apache Maven é uma ferramenta de automação e gerenciamento de projetos que fornece uma forma padronizada de automação, construção(build) e publicação de aplicações. O Maven é uma ferramenta bastante flexível permitindo estender suas funcionalidades nativas através da adição de plugins.
Quando falamos no Maven a funcionalidade mais lembrada é seu gerenciador de dependências, o que realmente é o que mais facilita a vida do desenvolvedor, mas o Maven possui outras funcionalidade como facilitar a compilação do código e seu empacotamento(JAR, WAR, etc), facilitando a execução de testes unitários e seus relatórios, gerando documentação, etc.
Ainda estimula boas práticas de desenvolvimento como a separação de classes de teste das classes do sistema, uso de convenções de nomes e diretórios, etc.
Alguns conceitos importantes do Maven
Antes de começarmos propriamente com a instalação e uso do maven devemos conhecer alguns conceitos que serão importantes.
Artefato
Um artefato é geralmente um arquivo JAR, mas pode ser de outro tipo, que fica armazenado em um repositório que podemos utilizar como dependência em nossos projetos, inclusive nosso projeto após compilado é considerado um artefato.
Um Artefato é identificado através dos seguintes elementos:
- Grupo: normalmente o endereço de um domínio ao contrário, por exemplo
com.google
,org.apache
,info.botecodigital
, etc. - Identificador único do artefato: normalmente o nome do projeto que se está criando ou nome da biblioteca.
- Número da versão: a versão do projeto, por exemplo:
1.3
,5.2.4
. Se o sufixo-SNAPSHOT
estiver presente( como1.8-SNAPSHOT
) significa que o projeto ainda está em desenvolvimento e pode ser alterado. - Tipo do projeto: jar, war, ear.
Repositório Local
Um repositório é um diretório no qual os artefatos baixados de um repositório remoto são armazenados em seu computador, é onde o maven roda. Os artefatos são baixados nele utilizando o comando install
do maven, ou quando utilizamos um artefato em nosso projetos e o próprio maven realiza o download automaticamente.
Repositório Remoto
Consiste em uma aplicação, normalmente disponibilizada de forma pública na internet onde criadores de bibliotecas e frameworks, disponibilizam seus artefatos para o publico utilizar. Também é possível criar repositórios remoto privados de empresas dentro da sua intranet utilizando programas como Artifactory.
O maven já vem com um repositório central já configurado onde as dependências são buscadas quando as declaramos em nosso projeto, mas é possível adicionar mais repositórios remotos.
Arquivo POM
O arquivo pom.xml
é o arquivo de configuração central do maven e está presente em todo projeto maven. É nele que declaramos as dependências do nosso projeto, os repositórios remotos adicionais, uso de plugin do maven entre outras configurações importantes.
Ciclo de Vida, fases e Goals
O Maven é construído sobre o conceito de ciclo de vida que define o processo de construção e distribuição de um determinado artefato. Existem três ciclos de vida default
, clean
e site
. Os ciclos são compostos por fases que são executadas sequencialmente. Quando é executado um comando para uma fase, todas as anteriores são executadas em sequencia até a fase alvo ser atingida.
Cada ciclo de vida possui várias fases, vamos ver algumas:
- Ciclo clean: A fase
clean
remove todos arquivos e diretórios criados pelo Maven como parte do build. - Ciclo site: A fase
site
gera a documentação do projeto. - Ciclo default: Compreende um conjunto de fases para lidar com o build e deploy do projeto, sendo elas:
validate
: valida se o projeto é válido e correto e se todas as informações estão disponíveiscompile
: compila o código fonte do projetotest
: roda testes unitários do projeto utilizando um framework de teste.package
: empacota o código compilado no seu formato de ditribuição como JAR.verify
: roda verificações nos resultados dos testes de integração para garantir que os critérios de qualidade sejam atendidos.install
: instala o pacote dentro do repositório local, para utilizar como dependência de outros projetos localmente.deploy
: copia o pacote final para o repositório remoto configurado.
Então se executarmos o maven para a fase install
, ele irá automaticamente inferir o ciclo de vida default e executar as fases: validade
, compile
, test
, package
, verify
e finalmente install
.
A lista de fases de cada ciclo de vida pode ser vista aqui.
Agora temos um pequena noção dos conceitos vamos para a prática.
Instalando o maven
No Linux instalar o maven é bastante simples, no Debian podemos utilizar o apt-get
sudo apt-get install maven
No windows é um pouco mais chato primeiro você deve fazer o download na página do projeto.
Após o download descompacte o conteúdo do zip para um diretório de sua escolha. Logo após, para utilizarmos o comando mvn
na linha de comando devemos adicionar seu caminho nas variáveis de ambiente. Para isso, no windows 10, entre em Painel de Controle -> Sistema e Segurança -> Sistema. Acesse Configurações avançadas de Sistema, depois Variáveis de Ambiente, em seguida Path e Editar, e adicione uma nova linha com o endereço da basta bin
no diretório no qual descompactou o maven.
Criando um projeto Maven
Para criar um projeto no maven utilizamos algum archetype, eles são uma espécie de projeto pré-definidos com algumas configurações já prontas. Para criar um projeto podemos utilizar o seguinte comando .
mvn archetype:generate
Ele irá realizar alguns downloads se necessário e listará um longa lista de archetypes disponíveis, compreendendo a iniciação de vários tipos de projetos e diversos frameworks. Após a listagem irá nos solicitar a escolha de um archetype por número associado, na tela de exemplo abaixo ele já sugere o archetype org.apache.maven.archetypes:maven-archetype-quickstart
pelo seu número 1593 na listagem.
Como pode ter imaginado, não é muito rápido encontrar o número de um archetype nesta longa lista, então se já soubermos o archetype do tipo de projeto que queremos criar podemos utilizar o seguinte comando.
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart
Como vimos acima utilizamos o parâmetro -DarchetypeArtifactId
para especificar o archetype escolhido, sem ser necessário a listagem. Durante a criação do projeto o maven solicitou as informações de groupId
, artifactId
, package
, informações que vimos no inicio do post. Estas informações são utilizadas para criar o arquivo pom.xml
. Segue abaixo o arquivo criado.
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>info.botecodigital</groupId>
<artifactId>exemplo-projeto-maven</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>exemplo-projeto-maven</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Além do arquivo pom.xml
, o maven também cria uma estrutura de diretório, esta estrutura pode variar um pouco dependendo do tipo de projeto, mas os principais são:
src/main/java
: onde fica o código-fonte do projeto.src/main/resources
: arquivos auxiliares do sistema, como.properties
, XMLs e configurações.src/main/webapp
: no caso de uma aplicação web, os arquivos JSP, HTML, JS, CSS vão aqui, incluindo oweb.xml
.src/test/java
: onde os teste unitários ficam, eles são executados automaticamente com o framework de teste configurado(por padrão utiliza JUnit que já vem como dependência do projeto).src/test/resources
: arquivos auxiliares usados nos testes, como arquivo de propriedades e configurações separados para os testes.pom.xml
: é o arquivo que concentra as configurações do seu projeto.target
: é o diretório para onde vai tudo que é construído pelo maven, como arquivos compilados, JARs, WARs, JavaDoc, entre outros.
Estes diretórios são uma convenção do maven e uma boa prática de separação dos artefatos do projeto, e ele trabalha automaticamente com eles, mas é possível utilizar outra estrutura para, por exemplo, utilizar o maven em um projeto já criado e com estrutura diferente. Veja como trocar o diretório dos arquivos fontes:
<project>
...
<build>
<sourceDirectory>fontes</sourceDirectory>
<directory>bin</directory>
<testSourceDirectory>testesdir</testSourceDirectory>
</build>
...
</project>
No trecho acima trocamos o diretório src/main/java
para fontes
, o diretório target
para bin
, e src/test/java
para testesdir
.
Adicionando uma dependência
Como falado no inicio do post, o principal recurso do maven é o gerenciamento de dependências. É muito comum em projetos necessitarmos de uma biblioteca e esta biblioteca necessitar de outras bibliotecas. Gerenciar isto manualmente pode ser um pesadelo, então o maven fornece um mecanismo de gerenciamento automático onde podemos declarar um dependência ficando a cargo do maven baixar ela e outras bibliotecas das quais esta necessita(dependências transitivas).
Para adicionar uma dependência primeiro temos que saber suas informações, um lugar bom para pesquisar é o MVNRepository que é um índice das dependências disponíveis no repositório do maven.
Nele podemos pesquisar pela biblioteca que desejamos e pegar o código/configuração pronto para inserir no pom.xml
. Para um exemplo simples vamos inserir a dependência da biblioteca commons-lang3
. Então primeiramente vamos buscá-lo.
Com o código em mãos adicione no seu pom.xml
, as configurações das dependências devem ser adicionada dentro da marcação dependencies
.
<project>
...
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
</dependency>
</dependencies>
...
</project>
Para configurar uma dependência é necessários três informações, seu groupId
, artifactId
e version
. Outra configuração que pode ser fornecida é o scope
. que pode ter os seguintes valores:
compile
*: Dependência necessária para compilação. Significa assim que é necessária para os testes e para runtime (projeto rodando). *É o scope padrão das dependências se nenhum for informado.test
: Dependência necessária somente para testes. Significa que esta dependência só é utilizada no código de teste, portanto não necessário para o runtime.runtime
: Dependência não necessária durante a compilação, mas para rodar o projeto.provided
: Dependência necessária para compilação e runtime, mas não empacotada com o projeto para distribuição. Esta dependência será fornecida pelo usuário. Um exemplo desta dependência éservlet-api
, em geral fornecida pelos servidores de aplicação.system
: Similar aprovided
, porém necessita ser indicado explicitamente a localização do arquivo JAR. Pouco utilizada.import
: Dependência utilizada para centralizar projetos com vários módulos.
Com a dependência adicionada em nosso projeto vamos usá-lo em uma classe:
package info.botecodigital;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class App
{
private static final Logger logger = LogManager.getLogger(App.class);
public static void main( String[] args )
{
logger.error("Algo de errado não está certo");
}
}
Podemos então utilizar o goal mvn install
para criar um jar do nosso projeto.
mvn install
Após a execução deste comando você irá encontra o arquivo exemplo-projeto-maven-1.0-SNAPSHOT.jar
dentro da pasta target
. E deve estar se perguntando, onde estão as dependências baixadas? Por padrão o maven não as copia para o diretório target
, mas podemos utilizar o comando dependency:copy-dependencies
para forçar ele a copiar as dependências para a pasta dependency
mvn install dependency:copy-dependencies
Após isso basta executar com o comando
java -cp target\exemplo-projeto-maven-1.0-SNAPSHOT.jar;target\dependency\* info.botecodigital.App
Outra forma seria criar um fat jar, com as dependências dentro dele, para isso temos que utilizar um plugin do maven, o maven-assembly-plugin, que deve ser adicionado dentro da marcação plugins
, e esta dentro da marcação build
, veja como ficaria:
<project>
...
<dependencies>
...
</dependencies>
..
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>info.botecodigital.App</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</project>
Como vimos adicionamos o plugin ao nosso pom.xml
informando seu artifactId
(maven-assembly-plugin
), configurando o manifest do pacote para que a classe main seja info.botecodigital.App
, deixando assim o jar executável. Na configuração descriptorRef
definimos que o jar deverá conter as dependências com a opção jar-with-dependencies
.
Agora para gerar o jar utilizamos o seguinte comando:
mvn compile assembly:single
Ele irá gerar um outro jar com o nome de exemplo-projeto-maven-1.0-SNAPSHOT-jar-with-dependencies.jar
, que podemos executar com o comando:
java -jar target\exemplo-projeto-maven-1.0-SNAPSHOT-jar-with-dependencies.jar
Podemos visualizar todas as dependências de um projeto utilizando o comando:
mvn dependency:list
Segue um exemplo para um projeto com um pouco mais de dependências utilizando o archetype: org.springframework.boot:spring-boot-sample-simple-archetype
Também podemos ver a árvore de dependências do projeto, isto ajuda a entender quais dependências dependem de quais. Fazemos isso com o seguinte comando:
mvn dependency:tree
Executando testes no maven
O maven como você já viu acima, também executa os teste unitário, para isto basta criá-los dentro da pasta de teste src/test/java
. Vejamos como exemplo duas classes de teste.
package info.botecodigital;
import junit.framework.TestCase;
public class AppTest extends TestCase{
public void testApp1(){
int x = 2+ 2;
assertEquals( 4 , x );
}
public void testApp2(){
String nome = "Rodrigo";
assertEquals( "Rodrigo" , nome );
}
}
package info.botecodigital;
import junit.framework.TestCase;
public class App2Test extends TestCase{
public void testApp4(){
int x = 10 + 10;
assertEquals( 19 , x );
}
}
Tanto a classe AppTest
e App2Test
estão no diretório src/test/java/info/botecodigita
l. Para excuta-las basta o comando:
mvn test
Lembrando que para executar automaticamente os testes, as classes devem seguir um determinado padrão de nome: "**/Test*.java"
, "**/*Test.java"
, "**/*Tests.java"
, "**/*TestCase.java"
.
Caso nossas classes de teste não sigam o padrão acima devemos inclui-las explicitamente para o teste com:
mvn -Dtest=AppTest,App2Test test
Bom para finalizar outra configuração importante para é qual versão do java iremos utilizar para compilar nosso projeto isto pode ser feito através da marcação de properties
:
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
O maven pode ser meio confuso no inicio, mas ele agrega muita organização e permite um gerenciamento mais fácil das dependências de um projeto, embora mesmo assim possa ocorrer alguns problemas como duas dependências exigirem versões diferentes de uma biblioteca.
Ele também é suportados ela maioria das IDE, permitindo realizar suas operações diretamente por ela sem ser necessário o uso da linha de comando.
É claro que o maven ainda disponibiliza vários outros recursos interessantes e muito mais configurações, mas espero ter passado o básico para poder começar a utilizar. Então T++ pessoal.