O que é uma dependência Java "sombreada"?

75

Desenvolvedor JVM aqui. Ultimamente, tenho visto brincadeiras nas salas de bate-papo do IRC e até em meu próprio escritório sobre as chamadas bibliotecas Java " sombreadas ". O contexto do uso será algo como:

" Tal e assim fornece um cliente" sombreado "para o XYZ " .

Exemplo perfeito é esse problema do Jira para o HBase : " Publique um artefato do cliente com dependências sombreadas "

Então, pergunto: O que é um JAR sombreado , o que significa ser "sombreado"?

smeeb
fonte

Respostas:

88

Dependências de sombreamento é o processo de inclusão e renomeação de dependências (realocando as classes e reescrevendo os códigos e recursos afetados) para criar uma cópia privada que você agrupa junto com seu próprio código .

O conceito é geralmente associado aos uber-jarros (também conhecidos como jarros de gordura ).

Há alguma confusão sobre o termo , devido ao plugin maven shade, que sob esse nome único faz duas coisas (citando sua própria página):

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.

Portanto, a parte de sombreamento é realmente opcional: o plug-in permite incluir dependências em seu jar (jar de gordura) e, opcionalmente, renomear dependências (sombreadas) .

Adicionando outra fonte :

Para sombrear uma biblioteca é pegar os arquivos de conteúdo da referida biblioteca, colocá-los em seu próprio jarro e alterar o pacote deles . Isso é diferente do empacotamento, que é simplesmente enviar os arquivos das bibliotecas ao lado do seu próprio jar sem realocá-los para um pacote diferente.

Tecnicamente falando, as dependências são sombreadas. Mas é comum referir-se a um jar de gordura com dependências sombreadas como "jar sombreado" e, se esse jar for um cliente para outro sistema, poderá ser chamado de "cliente sombreado".

Aqui está o título do problema do Jira para o HBase que você vinculou na sua pergunta:

Publicar um artefato do cliente com dependências sombreadas

Então, neste post, estou tentando apresentar os 2 conceitos sem confundi-los.

O bom

Os Uber-jars geralmente são usados ​​para enviar um aplicativo como um único arquivo (facilita a implantação e a execução). Eles também podem ser usados ​​para enviar bibliotecas junto com algumas (ou todas) de suas dependências sombreadas , para evitar conflitos quando usadas por outros aplicativos (que podem usar versões diferentes dessas bibliotecas).

Existem várias maneiras de criar uber-jars, mas maven-shade-pluginvai um passo além com seu recurso de realocação de classe :

Se o JAR uber for reutilizado como uma dependência de algum outro projeto, a inclusão direta de classes das dependências do artefato no JAR uber poderá causar conflitos de carregamento de classe devido a classes duplicadas no caminho da classe. Para solucionar esse problema, é possível realocar as classes incluídas no artefato sombreado para criar uma cópia privada de seu bytecode.

(Nota histórica: Jar Jar Links ofereceu esse recurso de realocação antes)

Portanto, com isso, você pode tornar as dependências da sua biblioteca um detalhe de implementação , a menos que exponha classes dessas bibliotecas na sua API.

Digamos que eu tenha um projeto, o ACME Quantanizer ™, que fornece DecayingSyncQuantanizerclasse e depende do Apache commons-rng (porque é claro que para quantanizar adequadamente você precisa de um XorShift1024Star, duh).

Se eu usar o plugin shadow maven para produzir um uber-jar e olhar para dentro, vejo esses arquivos de classe:

com/acme/DecayingSyncQuantanizer.class
org/apache/commons/rng/RandomProviderState.class
org/apache/commons/rng/RestorableUniformRandomProvider.class
...
org/apache/commons/rng/core/source64/XorShift1024Star.class
org/apache/commons/rng/core/util/NumberFactory.class

Agora, se eu usar o recurso de realocação de classe:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-shade-plugin</artifactId>
  <version>3.0.0</version>
  <executions>
    <execution>
      <phase>package</phase>
      <goals>
        <goal>shade</goal>
      </goals>
      <configuration>
        <relocations>
          <relocation>
            <pattern>org.apache.commons</pattern>
            <shadedPattern>com.acme.shaded.apachecommons</shadedPattern>
          </relocation>
        </relocations>
      </configuration>
    </execution>
  </executions>
</plugin>

O conteúdo do uber-jar é assim:

com/acme/DecayingSyncQuantanizer.class
com/acme/shaded/apachecommons/rng/RandomProviderState.class
com/acme/shaded/apachecommons/rng/RestorableUniformRandomProvider.class
...
com/acme/shaded/apachecommons/rng/core/source64/XorShift1024Star.class
com/acme/shaded/apachecommons/rng/core/util/NumberFactory.class

Não se trata apenas de renomear arquivos, ele reescreve o código de código que faz referência às classes realocadas (assim, minhas próprias classes e classes commons-rng são transformadas).

Além disso, o plug-in Shade também gerará um novo POM ( dependency-reduced-pom.xml) no qual as dependências sombreadas são removidas da <dependencies>seção. Isso ajuda a usar o frasco sombreado como uma dependência para outro projeto. Portanto, você pode publicar esse jar em vez do base, ou ambos (usando um qualificador para o jar sombreado).

Então isso pode ser muito útil ...

O mal

... mas também apresenta uma série de questões. A agregação de todas as dependências em um único "espaço para nome" dentro do jar pode ficar confusa e exigir sombreamento e confusão de recursos.

Por exemplo: como lidar com arquivos de recursos que incluem nomes de classe ou pacote? Arquivos de recursos, como descritores de provedores de serviços, nos quais todos vivem META-INF/services?

O plugin de sombra oferece transformadores de recursos que podem ajudar com isso:

A agregação de classes / recursos de vários artefatos em um JAR uber é direta, desde que não haja sobreposição. Caso contrário, é necessário algum tipo de lógica para mesclar recursos de vários JARs. É aqui que entram os transformadores de recursos .

Mas ainda é confuso e os problemas são quase impossíveis de prever (muitas vezes você descobre os problemas da maneira mais difícil na produção). Veja por que paramos de construir jarros de gordura .

Em suma, a implantação de um jar de gordura como um aplicativo / serviço independente ainda é muito comum, você só precisa estar ciente das dicas e, para algumas delas, pode ser necessário sombreamento ou outros truques.

O feio

Existem muitos problemas mais difíceis (depuração, testabilidade, compatibilidade com OSGi e carregadores de classes exóticos ...).

Mais importante, porém, quando você produz uma biblioteca, os vários problemas que você pensou que poderia controlar agora estão se tornando infinitamente mais complicados, porque seu jar será usado em muitos contextos diferentes (diferente de um jar de gordura que você implanta como aplicativo / serviço independente em um ambiente controlado).

Por exemplo, o ElasticSearch costumava ocultar algumas dependências dos jarros enviados, mas eles decidiram parar de fazer isso :

Antes da versão 2.0, o Elasticsearch era fornecido como um JAR com algumas (mas não todas) dependências comuns sombreadas e empacotadas no mesmo artefato. Isso ajudou os usuários de Java que incorporaram o Elasticsearch em seus próprios aplicativos a evitar conflitos de versão de módulos como Guava, Joda, Jackson etc. É claro que ainda havia uma lista de outras dependências não sombreadas, como Lucene, que ainda podiam causar conflitos.
Infelizmente, o sombreamento é um processo complexo e passível de erros, que resolveu problemas para algumas pessoas enquanto cria problemas para outras. O sombreamento torna muito difícil para os desenvolvedores e autores de plugins escrever e depurar o código corretamente porque os pacotes são renomeados durante a compilação. Por fim, costumávamos testar o Elasticsearch sem sombreamento e enviar o frasco sombreado, e não gostamos de enviar nada que não estamos testando.
Decidimos enviar o Elasticsearch sem sombreamento a partir de 2.0.

Observe que eles também se referem a dependências sombreadas , não a jarras sombreadas

Hugues M.
fonte
1
Obrigado por reservar um tempo para explicar isso. A documentação oficial do plugin maven shade é completamente inadequada e não discute nada disso, nem se preocupa em definir "uber jar". Essa documentação é obtusa e inútil. Sua redação é útil.
Cheeso
Excelente explicação, acho que deve ser incluída na documentação oficial
Adelin
7

Deixe-me responder à pergunta com a ajuda do software realmente responsável pela criação de frascos sombreados ... pelo menos ao usar o maven.

Retirado da página inicial do Apache Maven Shade Plugin :

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.

Um jar sombreado, também conhecido como uber-jar ou fat jar, conterá por padrão todas as dependências necessárias para executar o aplicativo Java, para que nenhuma dependência adicional seja necessária no caminho de classe. Você só precisa da versão Java correta para executar seu aplicativo. Um jar sombreado ajudará a evitar problemas de implantação / caminho de classe, mas será muito maior que o jar do aplicativo original e não ajudará a evitar o inferno do jar.

Jesko R.
fonte
1
Receoso que esta resposta esteja incompleta: explica o que são os jarros gordos / uber, mas não explica a parte do sombreamento . E sim, o sombreamento deve ajudar 100% com o "jar hell" (o que torna a última parte dessa resposta incorreta). Portanto, é útil em algum nível, mas aumenta a confusão: - /
Hugues M.
1
@HuguesMoreau Talvez eu não esteja 100% completo na minha resposta, mas ainda trouxe o ponto que eu queria enfatizar. Obrigado por trazer a peça que faltava para a mesa. O sombreamento não evitará o jar jar, foi o que eu quis dizer e escrevi, mas ele fornecerá algumas ferramentas disponíveis para resolver alguns dos problemas, mas não é automático. O que faz a última parte se lida e interpretada da maneira que eu quis dizer, pelo menos ok. :)
Jesko R.