A construção de um projeto Java geralmente envolve o uso de diversas bibliotecas externas que podem ser desenvolvidas por terceiros ou construídas in-house especificamente para um determinado projeto. Essas bibliotecas são normalmente distribuídas como arquivos JARs e disponibilizadas nos ambiente de execução (JVM) utilizando o classpath. Apesar dessa ser uma forma padrão adotada em aplicativos construídos em Java, em certos casos temos a necessidade de gerar um único arquivo JAR que contém tanto as classes do projeto quanto as classes de suas dependências. Esse JAR em questão é conhecido como Uber JAR.
Uma abordagem direta e mais imediata que vem na cabeça para gerar esse Uber JAR é extrair os arquivos de todos JARs que o nosso projeto depende em um único diretório e depois adicioná-los novamente em um único JAR. Mas será que precisamos fazer isso na mão? Bem, se você tem o costume de usar o Maven para compilar e empacotar seus projetos, a resposta é não! O Shade é um plugin do Maven que faz esse trabalho sujo para nós.
Vamos usar um projeto simples de exemplo para demonstrar o funcionamento do Shade. O objetivo desse projeto é simplesmente gerar uma mensagem de saudação no console utilizando o Spring Framework. No final, vamos gerar um Uber JAR executável contendo as classes do projeto e as classes e arquivos de configuração do Spring Framework. Para tanto, vamos começar construindo uma classe HelloWorld, apresentada a seguir, que contém um único método responsável por gerar a saudação.
O Spring framework será responsável por criar a instância dessa classe. Assim, vamos criar o arquivo de configuração do Spring, que basicamente define um bean chamado helloWorld do tipo da classe HelloWorld. A listagem abaixo mostra como fica o XML de configuração do contexto do Spring.
Por fim, criamos a classe que será responsável por inicializar o contexto do Spring, obter a instância do bean do contexto e chamar o método que gera a saudação. A listagem abaixo apresenta tal classe.
Agora que temos todos os artefatos necessários para o nosso projeto, vamos construir o pom.xml do Maven para o nosso projeto. O conteúdo desse arquivo aparece na seguinte listagem.
Esse arquivo deixa explicito na seção <dependencies> que o projeto depende do Spring Context. Na seção <build> é informado que o plugin Shade deve ser usado na fase de empacotamento (package) do projeto, ou seja, durante a geração do JAR do projeto deverá ser executado o Shade para copiar para o JAR gerado as classes que o projeto depende. Note que o Uber JAR gerado conterá todas as classes de todas as dependências resolvidas transitivamente, ou seja, as classes do Spring Context, as dependências do Spring Context, as dependências das dependências, e assim por diante.
Note ainda que nesse pom usamos os Resources Transformers do Shade na seção <configuration>. Os Resource Transformers são usado para agregar diversos recursos em um único arquivo. Por exemplo, no caso do Spring Framework, cada JAR contém um conjundo de arquivos XSD utilizados para validar os XMLs e dentro do META-INF de cada JAR existem dois arquivos chamados spring.schemas e spring.handlers que auxiliam na tarefa de "descobrir" onde estão os arquivos XSD. Como estamos juntando vários arquivos JARs do Spring e em cada um desses arquivos existe o spring.schemas e o spring.handlers, será necessário unir o conteúdo desses arquivos em um único arquivo que ficará no META-INF do Uber Jar. Essa junção dos arquivos pode ser feita pelo Shade, utilizando as seguintes definições:
Além disso, como o JAR gerado será um executável vamos incluir também no MANIFEST-MF qual é a classe executável do projeto, incluindo as seguintes linhas no pom.xml: