Che cos'è una dipendenza Java "ombreggiata"?


76

Sviluppatore JVM qui. Ultimamente ho visto le chiacchiere nelle chat room dell'IRC e persino nel mio ufficio sulle cosiddette librerie Java " ombreggiate ". Il contesto dell'uso sarà simile a:

" Tale e quindi fornisce un client" ombreggiato "per XYZ. "

L'esempio perfetto è questo problema di Jira per HBase : " Pubblica un artefatto client con dipendenze ombreggiate "

Quindi chiedo: che cos'è un JAR ombreggiato , cosa significa essere "ombreggiato"?

Risposte:


88

Le dipendenze di ombreggiatura sono il processo di inclusione e ridenominazione delle dipendenze (in tal modo riposizionando le classi e riscrivendo il codice e le risorse interessati) per creare una copia privata raggruppata insieme al proprio codice .

Il concetto è di solito associato a uber-jars (aka vasetti di grasso ).

C'è un po 'di confusione sul termine , a causa del plug-in Maven Shadow, che sotto quel singolo nome fa 2 cose (citando la propria pagina):

Questo plug-in offre la possibilità di impacchettare il manufatto in un Uber-Jar, comprese le sue dipendenze e di ombreggiare - cioè rinominare - i pacchetti di alcune dipendenze.

Quindi la parte shading è in realtà facoltativa: il plug-in consente di includere dipendenze nel tuo jar (fat jar) e facoltativamente rinominare (shading) dipendenze .

Aggiunta di un'altra fonte :

Ombreggiare una libreria significa prendere i file dei contenuti di detta libreria, metterli nel proprio vaso e cambiare il loro pacchetto . Questo è diverso dal packaging che sta semplicemente spedendo i file delle librerie nel tuo vaso senza trasferirli in un pacchetto diverso.

Tecnicamente parlando, le dipendenze sono ombreggiate. Ma è comune fare riferimento a un fat-jar-with-shaded-dependances come "jar ombreggiato", e se quel jar è un client per un altro sistema, può essere indicato come "client ombreggiato".

Ecco il titolo del problema di Jira per HBase che hai collegato nella tua domanda:

Pubblica un artefatto client con dipendenze ombreggiate

Quindi in questo post sto cercando di presentare i 2 concetti senza confonderli.

Il bene

Uber-jars sono spesso usati per spedire un'applicazione come un singolo file (facilita la distribuzione e l'esecuzione). Possono anche essere usati per spedire librerie insieme ad alcune (o tutte) delle loro dipendenze ombreggiate , al fine di evitare conflitti quando usate da altre applicazioni (che potrebbero usare versioni diverse di quelle librerie).

Esistono diversi modi per costruire uber-jars, ma maven-shade-pluginva oltre con la sua funzione di trasferimento di classe :

Se il JAR superiore viene riutilizzato come dipendenza di qualche altro progetto, l'inclusione diretta delle classi dalle dipendenze del manufatto nel JAR superiore può causare conflitti di caricamento delle classi a causa di classi duplicate sul percorso della classe. Per risolvere questo problema, è possibile riposizionare le classi che vengono incluse nell'artefatto ombreggiato al fine di creare una copia privata del loro bytecode.

(Nota storica: Jar Jar Links ha offerto prima quella funzione di trasferimento)

Quindi con questo puoi rendere le dipendenze delle tue librerie un dettaglio di implementazione , a meno che tu non esponga le classi da quelle librerie nella tua API.

Diciamo che ho un progetto, ACME Quantanizer ™, che fornisce DecayingSyncQuantanizerclasse e dipende da Apache commons-rng (perché ovviamente per quantanizzare correttamente è necessario un XorShift1024Star, duh).

Se uso il plugin ombra maven per produrre un Uber-Jar e guardo dentro, vedo questi file di 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

Ora se uso la funzione di trasferimento di 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>

Il contenuto di uber-jar è simile al seguente:

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

Non si tratta solo di rinominare i file, ma riscrive il bytecode che fa riferimento a classi trasferite (quindi, le mie classi e le mie classi comuni sono tutte trasformate).

Inoltre, il plugin Shade genererà anche un nuovo POM ( dependency-reduced-pom.xml) in cui le dipendenze ombreggiate vengono rimosse dalla <dependencies>sezione. Questo aiuta a usare il vaso ombreggiato come dipendenza per un altro progetto. Quindi puoi pubblicare quel vaso anziché quello di base o entrambi (usando un qualificatore per il vaso ombreggiato).

Quindi può essere molto utile ...

Il cattivo

... ma pone anche una serie di problemi. L'aggregazione di tutte le dipendenze in un singolo "spazio dei nomi" all'interno del barattolo può diventare confusa e richiedere ombre e confusione con le risorse.

Ad esempio: come gestire i file di risorse che includono nomi di classi o pacchetti? File di risorse come descrittori di provider di servizi che vivono tutti META-INF/services?

Il plugin ombra offre trasformatori di risorse che possono aiutare in questo:

L'aggregazione di classi / risorse da diversi artefatti in un unico JAR è semplice fintanto che non vi sono sovrapposizioni. Altrimenti, è necessario un qualche tipo di logica per unire le risorse da diversi JAR. È qui che entrano in gioco i trasformatori di risorse .

Ma è ancora disordinato e i problemi sono quasi impossibili da prevedere (abbastanza spesso scopri i problemi nel modo più difficile in produzione). Scopri perché abbiamo smesso di costruire vasetti grassi .

Tutto sommato, la distribuzione di un grosso vaso come app / servizio autonomo è ancora molto comune, devi solo essere consapevole dei gotcha e, per alcuni di questi, potresti aver bisogno di ombre o altri trucchi.

Il brutto

Ci sono molti problemi più difficili (debug, testabilità, compatibilità con OSGi e classloader esotici ...).

Ma ancora più importante, quando produci una libreria, i vari problemi che pensavi di poter controllare ora diventano infinitamente più complicati, perché il tuo jar verrà utilizzato in molti contesti diversi (a differenza di un fat jar che distribuisci come app / servizio autonomo in un ambiente controllato).

Ad esempio, ElasticSearch era solito ombreggiare alcune dipendenze nei barattoli che spedivano, ma hanno deciso di smettere di farlo :

Prima della versione 2.0, Elasticsearch era fornito come JAR con alcune (ma non tutte) dipendenze comuni ombreggiate e impacchettate all'interno dello stesso artefatto. Ciò ha aiutato gli utenti Java che hanno incorporato Elasticsearch nelle proprie applicazioni per evitare conflitti di versione di moduli come Guava, Joda, Jackson, ecc. Naturalmente, c'era ancora un elenco di altre dipendenze non ombreggiate come Lucene che potevano ancora causare conflitti.
Sfortunatamente, l'ombreggiatura è un processo complesso e soggetto a errori che ha risolto problemi per alcune persone, creando problemi per altri. L'ombreggiatura rende molto difficile per gli sviluppatori e gli autori di plug-in scrivere e eseguire il debug del codice correttamente perché i pacchetti vengono rinominati durante la compilazione. Alla fine, abbiamo usato per testare Elasticsearch non ombreggiato, quindi spedire il barattolo ombreggiato e non ci piace spedire nulla che non stiamo testando.
Abbiamo deciso di spedire Elasticsearch senza ombreggiatura dalla 2.0 in poi.

Si noti che si riferiscono anche a dipendenze ombreggiate , non a vaso ombreggiato


1
Grazie per il tempo dedicato a spiegarlo. La documentazione ufficiale del plug-in Maven Shadow è completamente inadeguata e non discute nulla di tutto ciò, né si preoccupa nemmeno di definire "Uber Jar". Tale documentazione è ottusa e inutile. Il tuo commento è utile.
Cheeso

Ottima spiegazione, penso che dovrebbe essere incluso nei documenti ufficiali
Adelin,

7

Vorrei rispondere alla domanda con l'aiuto del software effettivamente responsabile della creazione di vasetti ombreggiati ... almeno quando si utilizza Maven.

Tratto dalla home page del plug-in Apache Maven Shade :

Questo plugin offre la possibilità di impacchettare il manufatto in un Uber-Jar, comprese le sue dipendenze e di ombreggiare, ad esempio rinominare, i pacchetti di alcune dipendenze.

Un jar ombreggiato aka uber-jar aka fat jar conterrà per impostazione predefinita tutte le dipendenze necessarie per eseguire l'applicazione Java in modo che non sia necessaria alcuna dipendenza aggiuntiva nel percorso di classe. È necessaria solo la versione Java corretta per eseguire l'applicazione. Un vaso ombreggiato aiuterà ad evitare problemi di distribuzione / classpath, ma sarà molto più grande del vaso dell'applicazione originale e non aiuterà a evitare l'inferno del vaso.


1
Temo che questa risposta sia incompleta: spiega cosa sono i barattoli grassi / super, ma non spiega la parte di ombreggiatura . E sì, l'ombreggiatura dovrebbe essere utile al 100% con "jar hell" (il che rende errata l'ultima parte di quella risposta). Quindi è utile ad un certo livello ma aggiunge confusione: - /
Hugues M.

1
@HuguesMoreau Potrei non essere completo al 100% nella mia risposta, ma ha comunque portato il punto che volevo superare. Grazie per aver portato la parte mancante sul tavolo. Ombreggiare non eviterà l'inferno, è quello che intendevo e scrivevo, ma ti fornirà alcuni strumenti a portata di mano che ti permetteranno di risolvere alcuni dei suoi problemi, ma non è automatico. Il che rende l'ultima parte se letta e interpretata nel modo in cui la intendevo, almeno ok. :)
Jesko R.,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.