Gerenciando dependências com o Maven em projetos Java

Durante o desenvolvimento, é muito comum precisarmos utilizar bibliotecas de terceiros. Não vale a pena desenvolver novamente algo que outra pessoa já fez, testou, e dedicou tempo em análise. Por isso, os projetos que desenvolvemos sempre possuem dependências externas, ou seja, bibliotecas desenvolvidas por terceiros que precisam ser referenciadas pelo nosso projeto.
O Java permite a utilização dessas bibliotecas através da importação de pacotes, mas é necessário que a implementação desse pacote esteja em um lugar conhecido. Uma forma de fazer isso é baixando a biblioteca e adicionando a mesma no projeto java:
Agora imagine como esse número de bibliotecas pode crescer a ponto de ficar bastante complexo o gerenciamento das mesmas. É necessário controlar a versão, compatibilidade entre elas, enfim, um monte de coisas que podem comprometer o funcionamento do projeto. Por isso, é crucial ter um gerenciamento de dependências eficaz no projeto, para que você não perca tempo baixando e mantendo binários de bibliotecas, que inclusive podem comprometer a segurança do seu projeto (quem garante que um programa malicioso não foi adicionado a um projeto como uma biblioteca inocente?). Tendo isso em mente, quero te apresentar o Maven.
Maven to the rescue
O Maven é um gerenciador de build e dependências baseado no conceito de project object model (POM). Traduzindo, ele permite configurar as dependências dos projetos apontando para os identificadores das mesmas num arquivo chamado pom.xml.

Dessa forma, você não precisa se preocupar em procurar os arquivos jars das bibliotecas para adicioná-los ao classpath do seu projeto. Isso é feito pelo Maven, você só precisa se preocupar em informar o identificador daquela dependência para que ele saiba quem baixar. Além disso, ele gerencia os procedimentos que devem ser executados durante o build da sua aplicação. Pode não parecer, mas isso melhora e muito a vida do desenvolvedor que irá manter o projeto.
Instalação
Não poderia ser mais simples. É só baixar um arquivo compactado no site oficial e extrair o mesmo num diretório de sua escolha. Esse link explica direitinho como fazer a instalação e configuração das variáveis de ambiente, caso você tenha dúvidas.
Como funciona?
Uma vez entendidos os conceitos, o Maven é bem simples de usar. Vou descrever aqui os conceitos básicos para que um desenvolvedor seja capaz de iniciar e manter um projeto Maven.
Pom.xml
Esse é o mínimo necessário para um projeto utilizar o Maven. Esse arquivo contém tudo sobre o projeto: Dependências, propriedades, tudo que será executado durante o build. O exemplo mais simples de um arquivo pom.xml seria como o abaixo:
O arquivo acima basicamente informa o identificador único do projeto, composto de nome do pacote (groupId), nome do projeto (artifactId) e versão (version). Também é informado o tipo de empacotamento do artefato, i.e., o formato de arquivo que será o produto do build (no nosso exemplo, é o jar).
Repositórios
Você deve estar se perguntando, de onde o Maven baixa essas dependências? Ele usa como base o arquivo settings.xml que fica dentro da pasta conf do Maven e especifica o repositório que será usado para baixar as dependências.

Se nada for especificado (é o caso do exemplo cima), por padrão, é utilizado o repositório oficial do Maven. As dependências baixadas desse repositório remoto ficam em um repositório local no diretório .m2 do usuário, o que permite que o Maven funcione offline. Se estiver curioso, acesse esse link que discute sobre a configuração desse arquivo settings.xml.
Fases e Goals
Para executar o build, o Maven trabalha com a ideia de fases. O build tem um ciclo de vida que é composto dessas fases, por isso, ao construir um projeto Maven você precisa informar quais fases serão executadas ou serão ignoradas. As fases seguem uma ordem predefinida, você informa apenas quem será a última fase do build. Também é possível adicionar novas fases utilizando plugins, que precisam apenas ser configurados no arquivo pom.xml. Dentro de cada fase, existem goals ou metas a serem atingidas. Elas podem ser especificadas ou omitidas, e também podem ser configuradas no arquivo pom.xml.

Se estiver curioso, dá uma olhada na quantidade de fases disponíveis no Maven na documentação oficial.
Hello World!
Após discutir sobre a teoria, vamos à prática. Para executar o build no Maven eu preciso ter um projeto, claro. Para criar um projeto inicial, o Maven permite que eu utilize arquétipos, que são modelos pré-definidos de projetos para diferentes contextos. Vamos utilizar aqui o arquétipo mais simples, que é o quickstart. Execute o comando abaixo no seu terminal preferido:
$ mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart
Durante a execução, o Maven solicita as informações obrigatórias do pom.xml (metadados do artefato). Após confirmar a operação, o projeto será criado com sucesso.

O resultado será um projeto Hello World com um arquivo pom.xml, que é o necessário para executar um build no Maven.
Agora, se eu quiser fazer um build do projeto, preciso apenas rodar o comando:
$ mvn clean install
O comando acima irá rodar todas as fases do Maven, menos o deploy, pois é a última fase depois do install. Ao executar o build, você verá o log de erro informando que a versão do Java utilizada não é mais suportada. Isso ocorre pois o arquétipo usado para gerar o projeto é antigo. Dessa forma, você vai precisar configurar o pom.xml para informar que o projeto vai usar uma versão específica do Java. Como estou usando o Java 10, vou adicionar a seguinte configuração no meu pom.xml acima da tag <dependencies>:
Feito isso, o build será executado com sucesso usando o Java 10 (execute o comando mvn clean install):

Como resultado, será criado um diretório chamado target, que irá conter o produto do build. Como informamos no pom.xml que o tipo de empacotamento seria jar, ele irá gerar um arquivo com essa extensão:

Se tentarmos executar o jar gerado, vamos obter a seguinte mensagem: Não foi possível localizar nem carregar a classe principal (essa mensagem pode variar dependendo do sistema operacional). Isso acontece pois o jar gerado não empacota as dependências e nem as informações necessárias para executá-lo. Uma forma de fazer isso é utilizando um plugin muito útil do Maven: maven-jar-plugin. Ele permite que você informe a classe principal para que seja possível executá-la. Para utilizá-lo, adicione o seguinte trecho no arquivo pom.xml, dentro da tag <plugins>:
Na tag <mainClass> é informado o nome completo da classe, que é composto pelo pacote mais o nome do arquivo java. Execute o build novamente (comando mvn clean install) e veja que o jar gerado poderá ser executado com sucesso depois disso:

Adicionando dependências
Para te mostrar como trabalhar com dependências num projeto Maven, vamos fazer uma melhoria no projeto utilizando o Log4j para logar a mensagem. Como se trata de uma biblioteca externa, precisamos adicionar a dependência no projeto Maven. É bem simples de encontrar qualquer dependência utilizando o Maven Repository. O site exibe os identificadores da dependência desejada e você precisa apenas copiar o xml e colar no seu arquivo pom. Nesse exemplo, vou utilizar o log4j adicionando o trecho abaixo na tag <dependencies>:
Para utilizar o log4j no projeto, é necessário criar o arquivo log4j.properties no diretório resources. Com esse diretório não existe, vamos criá-lo na pasta raiz do projeto e colocar o arquivo com o conteúdo abaixo dentro dele:
A estrutura do projeto deverá estar como descrito pela imagem abaixo:

Agora, é só trocar o System.out.println pelo Logger do log4j. Para isso, vamos editar a classe App.java (localizada em src/main/java/br/com/projeto) da seguinte forma:
Pronto! Vamos rodar o build (mvn clean install) e executar o jar gerado na pasta target:

O erro acima mostra que a dependência do Log4j não foi encontrada, mas não ocorreu erro de compilação no build. Isso ocorre porque por padrão o Maven não empacota as dependências. É necessário então informar onde ele deve buscá-las, ou gerar um fat jar que as contenha. Por simplicidade, vamos adotar a segunda opção. Para isso, será necessário usar outro plugin muito útil do Maven: maven-assembly-plugin. Para usá-lo, vamos adicionar o seguinte trecho no arquivo pom.xml dentro da tag <plugins>:
Agora vamos ter uma pequena mudança no comando do build. Para gerar o jar com dependências, é necessário usar o comando mvn compile assembly:single, pois o plugin vincula a geração do fat jar à fase assembly. Como resultado, será gerado um jar com o sufixo jar-with-dependencies, e é esse jar que vamos executar:

O erro ocorreu pois o Maven não empacotou o log4j.properties, que é o arquivo que contém os appenders do Log4j. Para que o Maven enxergue esse arquivo no build, será necessário adicionar no pom.xml essa informação através da tag <resources>, que será adicionada dentro da tag <build>:
Essa tag reconhece tudo que existe dentro do diretório resources como algo a ser empacotado dentro do fat jar. Dessa forma, durante a execução, o programa irá encontrar o appender do Log4j configurado no arquivo de propriedades. Vamos testar? Temos que rodar o build novamente (mvn clean compile assembly:single) e executar o jar:

💡 Se você preferir não usar a linha de comando, pode adicionar o Maven numa IDE para usar as funcionalidades gráficas. No Eclipse, por exemplo, você tem suporte ao Maven através do plugin m2e.
Conclusão
Tentei resumir o que acho mais relevante sobre o Maven e os principais erros encontrados durante o desenvolvimento de um projeto simples. E então, o que achou? Teve algum problema que não foi coberto nesse post? Comenta aqui para evoluirmos no assunto.
Se você gosta do meu conteúdo, não deixe de conferir o meu canal do Youtube, onde falo sobre desenvolvimento de software. Espero te ver por lá! 😉