Para que serve o maven-shade-plugin e por que você deseja realocar os pacotes Java?

281

Encontrei o maven-shade-plugin sendo usado no pom.xml de alguém. Eu nunca usei o maven-shade-plugin antes (e sou um Maven n00b), então tentei entender o motivo de usá-lo e o que ele faz.

Eu olhei para os documentos do Maven , mas não consigo entender esta afirmação:

"Este plugin fornece a capacidade de empacotar o artefato em um uber-jar, incluindo suas dependências e sombrear - ou seja, renomear - os pacotes de algumas das dependências."

A documentação da página não parece muito amigável para iniciantes.

O que é um "uber jar?" Por que alguém iria querer fazer um? Qual o sentido de renomear os pacotes das dependências? Tentei seguir os exemplos na página apache do maven-shade-plugin, como "Selecionando conteúdo para o Uber Jar", mas ainda não consigo entender o que está sendo realizado com "sombreamento".

Qualquer indicação de exemplos / casos de uso ilustrativos (com uma explicação de por que o sombreamento foi necessário nesse caso - que problema está resolvendo) seria apreciada. Por fim, quando devo usar o maven-shade-plugin?

não ser
fonte
16
Para o nome "uber jar", tenho a única associação com o alemão, onde "über" significa "over". Juntos, literalmente, significa "jar-over-all-other-jars". "Sombreamento" é o mesmo que "realocação de pacote", necessária para classes ou recursos que colidem.
21413 D13_13
1
s/reallocation/relocation/no comentário acima.
22420 Christopher Christopher Schultz
12
O uber jar é como um anel: um jarro para governar todos eles, um jarro para encontrá-los, um jarro para trazê-los todos e na escuridão prendê-los.
Marti Nito

Respostas:

342

Uber JAR, em suma, é um JAR que contém tudo.

Normalmente, no Maven, contamos com o gerenciamento de dependências. Um artefato contém apenas as classes / recursos de si mesmo. Maven será responsável por descobrir todos os artefatos (JARs etc.) que o projeto depende de quando o projeto for construído.

Um uber-jar é algo que pega todas as dependências e extrai o conteúdo das dependências e as coloca com as classes / recursos do próprio projeto, em um grande JAR. Por ter esse uber-jar, é fácil para a execução, porque você precisará de apenas um JAR grande em vez de toneladas de JARs pequenos para executar seu aplicativo. Também facilita a distribuição em alguns casos.

Apenas uma nota lateral. Evite usar o uber-jar como dependência do Maven, pois isso está arruinando o recurso de resolução de dependência do Maven. Normalmente, criamos o uber-jar apenas para o artefato final para implantação real ou para distribuição manual, mas não para colocação no repositório Maven.


Atualização: Acabei de descobrir que não respondi a uma parte da pergunta: "Qual é o sentido de renomear os pacotes das dependências?". Aqui estão algumas breves atualizações e, esperançosamente, ajudarão as pessoas com perguntas semelhantes.

Criar o uber-jar para facilitar a implantação é um caso de uso do plugin de sombra. Há também outros casos de uso comuns que envolvem renomeação de pacotes.

Por exemplo, estou desenvolvendo uma Foobiblioteca, que depende de uma versão específica (por exemplo, 1.0) da Barbiblioteca. Supondo que eu não possa fazer uso de outra versão da Barlib (porque alterações na API ou outros problemas técnicos, etc.). Se eu simplesmente declarar Bar:1.0como Foodependência do Maven, é possível cair em um problema: Um Quxprojeto depende Fooe também Bar:2.0(e não pode ser usado Bar:1.0porque Quxprecisa usar o novo recurso Bar:2.0). Aqui está o dilema: deve Quxusar Bar:1.0(cujo Quxcódigo não funcionará) ou Bar:2.0(queFoo código não funcionará)?

Para resolver esse problema, o desenvolvedor de Foopode optar por usar o plugin de sombra para renomear o uso de Bar, para que todas as classes no Bar:1.0jar sejam incorporadas no Foojar e o pacote das Barclasses incorporadas seja alterado de com.barpara com.foo.bar. Ao fazer isso, Quxpode com segurança depender, Bar:2.0porque agora Foonão depende mais Bare está usando sua própria cópia de "alterado", Barlocalizada em outro pacote.

Adrian Shum
fonte
5
Obrigado, isso ajuda muito. É chamado de "sombreamento" porque oculta as dependências e outros frascos dentro do uber-jar?
ser
6
Não tenho muita certeza sobre o motivo por trás do nome, mas, na primeira página, parece sugerir que o "sombreamento" está descrevendo o "oculto" das dependências. Como você pode opcionalmente incluir algumas dependências no JAR sombreado, o plugin de sombra também gerará um POM correto para você, que removerá as dependências incluídas. Parece que o sombreamento está descrevendo este processo
Adrian Shum
1
@AdrianShum: Você pode sugerir como criar um uber jar, sem usar o plugin de sombra? Ou especificamente como resolver a "ruína do recurso de resolução de dependência do Maven"?
Suraj Menon
3
@SurajMenon: Usar o plugin Shade é a maneira mais fácil de criar uber-jar. No entanto, você também pode usar o plug-in Assembly e usar o jar-with-dependencydescritor interno. Para problemas de exclusão de dependência usando o uber-jar, já mencionei na minha resposta: Não use o uber-jar como dependência, ponto final. Um pouco mais detalhadamente: antes de criar o uber-jar, você deve ter um projeto normal com dependência normal. Esse artefato original é o que você deve usar como dependência (em vez do uber-jar)
Adrian Shum
1
Apenas uma pequena questão: ele pára Class.forNamede funcionar?
SOFe 6/03/2017
64

Recentemente, fiquei me perguntando por que a pesquisa elástica obscurece e realoca algumas (mas não todas) de suas dependências. Aqui está uma explicação do mantenedor do projeto, @kimchy :

A parte de sombreamento é intencional, as bibliotecas sombreadas que usamos na elasticsearch são parte integrante da pesquisa elástica, a versão usada está intimamente ligada ao que a elasticsearch expõe e como ela usa a biblioteca com base nos detalhes internos de como a biblioteca funciona (e que muda entre versões), netty e goiaba são ótimos exemplos.

Aliás, não tenho nenhum problema em fornecer vários frascos de pesquisa elástica, um com lucene não sombreado e outro com lucene sombreado. Não tenho certeza como fazê-lo com maven embora. Não quero fornecer uma versão que não sombreie netty / jackson, por exemplo, por causa do uso intimidador profundo que a elasticsearch tem com eles (por exemplo, usar a próxima melhoria de armazenamento em buffer com qualquer versão anterior do netty, exceto a atual na verdade, use mais memória em comparação com o uso consideravelmente menor).

- https://github.com/elasticsearch/elasticsearch/issues/2091#issuecomment-7156766

E outro aqui de drewr :

O sombreamento é importante para manter nossas dependências (principalmente netty, lucene, goiaba) próximas ao nosso código, para que possamos corrigir um problema, mesmo que o provedor upstream fique para trás. É possível que distribuamos versões modularizadas do código, o que ajudaria em seu problema específico (nº 2091, por exemplo), mas não podemos simplesmente remover as dependências sombreadas no momento. Você pode criar uma versão local do ES para seus propósitos até que haja uma solução melhor.

- https://github.com/elasticsearch/elasticsearch/pull/3244#issuecomment-20125452

Então, esse é um caso de uso. Como exemplo ilustrativo, abaixo está como o maven-shade-plugin é usado no pom.xml da elasticsearch (v0.90.5). oartifactSet::include linhas o instruem sobre quais dependências extrair no JAR uber (basicamente, são descompactadas e empacotadas juntamente com as próprias classes do elasticsearch quando o jar de elasticsearch de destino é produzido. (Caso você ainda não saiba disso, um arquivo JAR é apenas um arquivo ZIP que contém as classes, recursos, etc. do programa e alguns metadados. Você pode extrair um para ver como ele é montado.)

As relocations::relocationlinhas são semelhantes, exceto que, em cada caso, elas também aplicam as substituições especificadas às classes de dependência - nesse caso, trazendo-as para baixo org.elasticsearch.common.

Finalmente, a filtersseção exclui algumas coisas do JAR de destino que não deveriam estar lá - como metadados JAR, arquivos de construção de formigas, arquivos de texto etc. que são empacotados com algumas dependências, mas que não pertencem a um JAR superior.

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>2.1</version>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>shade</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <minimizeJar>true</minimizeJar>
            <artifactSet>
                <includes>
                    <include>com.google.guava:guava</include>
                    <include>net.sf.trove4j:trove4j</include>
                    <include>org.mvel:mvel2</include>
                    <include>com.fasterxml.jackson.core:jackson-core</include>
                    <include>com.fasterxml.jackson.dataformat:jackson-dataformat-smile</include>
                    <include>com.fasterxml.jackson.dataformat:jackson-dataformat-yaml</include>
                    <include>joda-time:joda-time</include>
                    <include>io.netty:netty</include>
                    <include>com.ning:compress-lzf</include>
                </includes>
            </artifactSet>
            <relocations>
                <relocation>
                    <pattern>com.google.common</pattern>
                    <shadedPattern>org.elasticsearch.common</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>gnu.trove</pattern>
                    <shadedPattern>org.elasticsearch.common.trove</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>jsr166y</pattern>
                    <shadedPattern>org.elasticsearch.common.util.concurrent.jsr166y</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>jsr166e</pattern>
                    <shadedPattern>org.elasticsearch.common.util.concurrent.jsr166e</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>org.mvel2</pattern>
                    <shadedPattern>org.elasticsearch.common.mvel2</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>com.fasterxml.jackson</pattern>
                    <shadedPattern>org.elasticsearch.common.jackson</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>org.joda</pattern>
                    <shadedPattern>org.elasticsearch.common.joda</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>org.jboss.netty</pattern>
                    <shadedPattern>org.elasticsearch.common.netty</shadedPattern>
                </relocation>
                <relocation>
                    <pattern>com.ning.compress</pattern>
                    <shadedPattern>org.elasticsearch.common.compress</shadedPattern>
                </relocation>
            </relocations>
            <filters>
                <filter>
                    <artifact>*:*</artifact>
                    <excludes>
                        <exclude>META-INF/license/**</exclude>
                        <exclude>META-INF/*</exclude>
                        <exclude>META-INF/maven/**</exclude>
                        <exclude>LICENSE</exclude>
                        <exclude>NOTICE</exclude>
                        <exclude>/*.txt</exclude>
                        <exclude>build.properties</exclude>
                    </excludes>
                </filter>
            </filters>
        </configuration>
    </plugin>
</plugins>
Tom
fonte
2

Pequeno aviso

Embora isso não descreva por que alguém gostaria de usar o plugin maven-shade-plugin (como a resposta selecionada o descreve muito bem), gostaria de observar que tive problemas com ele. Ele mudou o JAR (desde que o que está fazendo) e causou regressão no meu software.

Então, em vez de usar este (ou o maven-jarjar-plugin), usei o binário do JarJar, que parece funcionar sem problemas.

Estou postando aqui minha solução, pois demorei algum tempo para encontrar uma solução decente.


Baixar o arquivo JAR do JarJar

Você pode fazer o download do jar aqui: https://code.google.com/p/jarjar/ No menu à esquerda, você tem um link para fazer o download.


Como usar o JarJar para realocar classes de um JAR de um pacote para outro

Neste exemplo, mudaremos o pacote de "com.fasterxml.jackson" para "io.kuku.dependencies.com.fasterxml.jackson". - O JAR de origem é chamado "jackson-databind-2.6.4.jar" e o novo JAR modificado (de destino) é chamado "kuku-jackson-databind-2.6.4.jar". - O arquivo JAR "jarjar" está na versão 1.4

  1. Crie um arquivo "rules.txt". O conteúdo do arquivo deve ser (observe o período antes do caractere '@'): regra com.fasterxml.jackson. ** io.kuku.dependencies.com.fasterxml.jackson. @ 1

  2. Execute o seguinte comando: java -jar jarjar-1.4.jar process rules.txt jackson-databind-2.6.4.jar kuku-jackson-databind-2.6.4.jar


Instalando os JARs modificados no repositório local

Nesse caso, estou instalando 3 arquivos localizados na pasta "c: \ my-jars \".

mvn install: install-file -Dfile = C: \ meus-jarros \ kuku-jackson-anotações-2.6.4.jar -DgroupId = io.kuku.dependencies -DartifactId = kuku-jackson-anotações -Dversion = 2.6.4 - Dpackaging = jar

mvn install: install-file -Dfile = C: \ my-jars \ kuku-jackson-core-2.6.4.jar -DgroupId = io.kuku.dependencies -DartifactId = kuku-jackson-core -Dversion = 2.6.4 - Dpackaging = jar

mvn install: install-file -Dfile = C: \ meus-jars \ kuku-jackson-databind-2.6.4.jar -DgroupId = io.kuku.dependencies -DartifactId = kuku-jackson-anotações -Dversion = 2.6.4 - Dpackaging = jar


Usando os JARs modificados no pom do projeto

Neste exemplo, este é o elemento "dependencies" nos projetos pom:

<dependencies>
    <!-- ================================================== -->
    <!-- kuku JARs -->
    <!-- ================================================== -->
    <dependency>
        <groupId>io.kuku.dependencies</groupId>
        <artifactId>kuku-jackson-annotations</artifactId>
        <version>2.6.4</version>
    </dependency>
    <dependency>
        <groupId>io.kuku.dependencies</groupId>
        <artifactId>kuku-jackson-core</artifactId>
        <version>2.6.4</version>
    </dependency>
    <dependency>
        <groupId>io.kuku.dependencies</groupId>
        <artifactId>kuku-jackson-databind</artifactId>
        <version>2.6.4</version>
    </dependency>
</dependencies>
nadavy
fonte
1
Obrigado por esta sugestão alternativa. Não era isso que eu procurava, mas acabou sendo uma solução muito mais simples e rápida para aplicar traduções únicas de pacotes em bibliotecas herdadas que nunca serão alteradas.
Frelling 31/03/16
você descobriu o motivo de tais falhas comparando o conteúdo de suas respectivas saídas?
Tribbloid 12/09/19
2

Acho que um exemplo da necessidade de um jar "sombreado" é uma função do AWS Lambda. Eles parecem permitir apenas o upload de 1 jar, não uma coleção inteira de .jars como você encontraria em um arquivo .war típico. Portanto, criar um único .jar com todas as dependências do projeto permite fazer isso.

atom88
fonte