Un programma può dipendere da una libreria durante la compilazione ma non dal runtime?


110

Capisco la differenza tra runtime e compilazione e come distinguere tra i due, ma semplicemente non vedo la necessità di fare una distinzione tra le dipendenze in fase di compilazione e runtime .

Quello su cui sto soffocando è questo: come può un programma non dipendere da qualcosa in fase di esecuzione da cui dipendeva durante la compilazione? Se la mia app Java utilizza log4j, ha bisogno del file log4j.jar per la compilazione (il mio codice si integra con e richiama i metodi dei membri dall'interno di log4j) e il runtime (il mio codice non ha assolutamente alcun controllo su ciò che accade una volta che il codice all'interno di log4j .jar è eseguito).

Sto leggendo strumenti per la risoluzione delle dipendenze come Ivy e Maven e questi strumenti fanno chiaramente la distinzione tra questi due tipi di dipendenze. Semplicemente non ne capisco la necessità.

Qualcuno può dare una semplice spiegazione del tipo "King's English", preferibilmente con un esempio reale che anche un povero idiota come me potrebbe capire?


2
È possibile utilizzare la reflection e utilizzare classi che non erano disponibili in fase di compilazione. Pensa a "plugin".
Per Alexandersson

Risposte:


64

Una dipendenza in fase di compilazione è generalmente richiesta in fase di esecuzione. In Maven, una compiledipendenza con ambito verrà aggiunta al classpath in fase di esecuzione (ad esempio, in guerre verranno copiate in WEB-INF / lib).

Tuttavia, non è strettamente richiesto; per esempio, possiamo compilare su una certa API, rendendola una dipendenza in fase di compilazione, ma poi in fase di esecuzione includere un'implementazione che include anche l'API.

Potrebbero esserci casi marginali in cui il progetto richiede una certa dipendenza per la compilazione, ma il codice corrispondente non è effettivamente necessario, ma questi saranno rari.

D'altra parte, è molto comune includere le dipendenze di runtime che non sono necessarie in fase di compilazione. Ad esempio, se stai scrivendo un'applicazione Java EE 6, compila con l'API Java EE 6, ma in fase di runtime è possibile utilizzare qualsiasi contenitore Java EE; è questo contenitore che fornisce l'implementazione.

Le dipendenze in fase di compilazione possono essere evitate utilizzando la reflection. Ad esempio, un driver JDBC può essere caricato con un Class.forNamee la classe effettiva caricata può essere configurata tramite un file di configurazione.


17
Informazioni sull'API Java EE: non è questo lo scopo dell'ambito di dipendenza "fornito"?
Kevin

15
un esempio in cui è necessaria una dipendenza per la compilazione ma non necessaria in fase di esecuzione è lombok (www.projectlombok.org). Il jar viene utilizzato per trasformare il codice java in fase di compilazione ma non è affatto necessario in fase di runtime. Specificando l'ambito "fornito", il jar non verrà incluso in war / jar.
Kevin

2
@ Kevin Sì, buon punto, l' providedambito aggiunge una dipendenza dal tempo di compilazione senza aggiungere una dipendenza dal runtime sull'aspettativa che la dipendenza verrà fornita al runtime con altri mezzi (ad esempio una libreria condivisa nel contenitore). runtimed'altra parte aggiunge una dipendenza di runtime senza renderla una dipendenza in fase di compilazione.
Artefacto

Quindi è sicuro affermare che di solito c'è una correlazione 1: 1 tra una "configurazione del modulo" (usando i termini Ivy) e la directory principale sotto la radice del progetto? Ad esempio, tutti i miei test JUnit che dipendono dal JAR JUnit saranno sotto il test / root, ecc. Semplicemente non vedo come le stesse classi, impacchettate nella stessa root di origine, possano essere "configurate" per dipendere da differenti JAR in qualsiasi momento. Se hai bisogno di log4j, allora hai bisogno di log4j; non c'è modo di dire allo stesso codice di richiamare le chiamate log4j sotto 1 configurazione, ma di ignorare le chiamate log4j sotto alcune configurazioni "non di registrazione", giusto?
IAmYourFaja

30

Ogni dipendenza Maven ha uno scope che definisce su quale classpath è disponibile quella dipendenza.

Quando crei un JAR per un progetto, le dipendenze non vengono raggruppate con l'artefatto generato; sono usati solo per la compilazione. (Tuttavia, puoi ancora fare in modo che Maven includa le dipendenze nel jar creato, vedi: Includere le dipendenze in un jar con Maven )

Quando si utilizza Maven per creare un file WAR o EAR, è possibile configurare Maven per raggruppare le dipendenze con l'artefatto generato ed è anche possibile configurarlo per escludere determinate dipendenze dal file WAR utilizzando l'ambito fornito.

L'ambito più comune - Compile Scope - indica che la dipendenza è disponibile per il progetto sul percorso di classe di compilazione, i percorsi di classe di compilazione ed esecuzione di unit test e l'eventuale percorso di classe di runtime quando si esegue l'applicazione. In un'applicazione Web Java EE, ciò significa che la dipendenza viene copiata nell'applicazione distribuita. In un file .jar, tuttavia, le dipendenze non verranno incluse nell'ambito della compilazione.

L'ambito di runtime indica che la dipendenza è disponibile per il progetto dall'esecuzione di test di unità e dai percorsi di classe di esecuzione di runtime, ma a differenza dell'ambito di compilazione non è disponibile quando si compila l'applicazione o i relativi test di unità. Una dipendenza runtime viene copiata nell'applicazione distribuita, ma non è disponibile durante la compilazione! Questo è utile per assicurarti di non dipendere per errore da una libreria specifica.

Infine, l' ambito fornito indica che il contenitore in cui viene eseguita l'applicazione fornisce la dipendenza per conto dell'utente. In un'applicazione Java EE, ciò significa che la dipendenza è già sul percorso di classe del contenitore Servlet o del server delle applicazioni e non viene copiata nell'applicazione distribuita. Significa anche che hai bisogno di questa dipendenza per compilare il tuo progetto.


@Koray Tugay La risposta è più precisa :) Ho una domanda veloce che dice che ho un vaso dipendente con l'ambito del tempo di esecuzione. L'esperto cercherà il vaso in fase di compilazione?
gks

@gks No, non lo richiederà in fase di compilazione.
Koray Tugay

9

In fase di compilazione sono necessarie dipendenze che potrebbero essere necessarie in fase di esecuzione. Tuttavia molte librerie vengono eseguite senza tutte le sue possibili dipendenze. cioè una libreria che può usare quattro diverse librerie XML, ma ne ha bisogno solo una per funzionare.

Molte biblioteche necessitano a turno di altre biblioteche. Queste librerie non sono necessarie in fase di compilazione ma sono necessarie in fase di esecuzione. cioè quando il codice viene effettivamente eseguito.


potresti fornirci esempi di tali librerie che non saranno necessarie durante la compilazione ma saranno necessarie in fase di esecuzione?
Cristiano

1
@Cristiano tutte le librerie JDBC sono così. Inoltre librerie che implementano un'API standard.
Peter Lawrey,

4

In generale hai ragione e probabilmente è la situazione ideale se le dipendenze di runtime e tempo di compilazione sono identiche.

Ti darò 2 esempi quando questa regola non è corretta.

Se la classe A dipende dalla classe B che dipende dalla classe C che dipende dalla classe D dove A è la tua classe e B, C e D sono classi di diverse librerie di terze parti, hai bisogno solo di B e C in fase di compilazione e hai bisogno anche di D in runtime. Spesso i programmi utilizzano il caricamento dinamico delle classi. In questo caso non hai bisogno di classi caricate dinamicamente dalla libreria che stai usando in fase di compilazione. Inoltre spesso la libreria sceglie quale implementazione utilizzare in fase di runtime. Ad esempio SLF4J o Commons Logging possono modificare l'implementazione del log di destinazione in fase di runtime. È necessario solo SSL4J stesso al momento della compilazione.

Esempio opposto quando hai bisogno di più dipendenze in fase di compilazione che in fase di runtime. Pensa che stai sviluppando un'applicazione che deve funzionare in ambienti o sistemi operativi diversi. Sono necessarie tutte le librerie specifiche della piattaforma in fase di compilazione e solo le librerie necessarie per l'ambiente corrente in fase di runtime.

Spero che le mie spiegazioni siano d'aiuto.


Puoi spiegare perché C è necessario in fase di compilazione nel tuo esempio? Ho l'impressione (da stackoverflow.com/a/7257518/6095334 ) che se C sia necessario o meno in fase di compilazione dipende da quali metodi e campi (da B) A fa riferimento.
Hervian


2

Ho appena riscontrato un problema che risponde alla tua domanda. servlet-api.jarè una dipendenza temporanea nel mio progetto web ed è necessaria sia in fase di compilazione che in fase di esecuzione. Ma servlet-api.jarè anche incluso nella mia libreria Tomcat.

La soluzione qui è rendere servlet-api.jardisponibile in Maven solo in fase di compilazione e non impacchettato nel mio file war in modo che non vada in conflitto con il servlet-api.jarcontenuto nella mia libreria Tomcat.

Spero che questo spieghi il tempo di compilazione e la dipendenza dal runtime.


3
Il tuo esempio in realtà non è corretto per una determinata domanda, perché spiega la differenza tra compilee providedscopes e non tra compilee runtime. Compile scopeè necessario in fase di compilazione ed è incluso nella tua app. Provided scopeè necessario solo in fase di compilazione ma non è impacchettato nella tua app perché è fornito con altri significati, ad esempio è già nel server Tomcat.
MJar

1
Beh, penso che questo è un piuttosto buona esempio perché la questione era per quanto riguarda tempo di compilazione e di runtime dipendenze e non su compilee runtime ambiti di Maven . Lo providedscopo è il modo in cui Maven gestisce il caso in cui una dipendenza in fase di compilazione non dovrebbe essere inclusa nel pacchetto runtime.
Christian Gawron

1

Capisco la differenza tra runtime e compilazione e come distinguere tra i due, ma non vedo la necessità di fare una distinzione tra le dipendenze in fase di compilazione e in runtime.

I concetti generali in fase di compilazione e runtime e le dipendenze specifiche compilee di runtimeambito di Maven sono due cose molto diverse. Non è possibile confrontarli direttamente poiché questi non hanno lo stesso frame: i concetti generali di compilazione e runtime sono ampi mentre i concetti di maven compilee runtimescope riguardano specificamente la disponibilità / visibilità delle dipendenze in base al tempo: compilazione o esecuzione.
Non dimenticare che Maven è soprattutto un javac/ javawrapper e che in Java hai un classpath in fase di compilazione che specifichi con javac -cp ... e un classpath runtime che specifichi con java -cp ....
Non sarebbe sbagliato considerare l' compileambito Maven come un modo per aggiungere una dipendenza sia nella compilazione Java che nel percorso di classe runtime (javace java) mentre l' runtimeambito Maven può essere visto come un modo per aggiungere una dipendenza solo nel classppath ( javac) del runtime Java .

Quello su cui sto soffocando è questo: come può un programma non dipendere da qualcosa in fase di esecuzione da cui dipendeva durante la compilazione?

Ciò che descrivi non ha alcuna relazione runtimee compilescopo.
Sembra più l' providedambito specificato per una dipendenza da dipendere da quello in fase di compilazione ma non in fase di runtime.
Lo usi perché ti serve la dipendenza da compilare ma non vuoi includerlo nel componente pacchettizzato (JAR, WAR o qualsiasi altro) perché la dipendenza è già fornita dall'ambiente: può essere inclusa nel server o in qualsiasi altro percorso del classpath specificato all'avvio dell'applicazione Java.

Se la mia app Java utilizza log4j, ha bisogno del file log4j.jar per compilare (il mio codice si integra e richiama i metodi dei membri dall'interno di log4j) così come il runtime (il mio codice non ha assolutamente alcun controllo su ciò che accade una volta che il codice si trova all'interno di log4j .jar è eseguito).

In questo caso sì. Ma supponiamo di dover scrivere un codice portabile che si basi su slf4j come facciata davanti a log4j per poter passare successivamente a un'altra implementazione di registrazione (log4J 2, logback o qualsiasi altra).
In questo caso in te pom devi specificare slf4j come compiledipendenza (è l'impostazione predefinita) ma specificherai la dipendenza log4j come runtimedipendenza:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>...</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>...</version>
    <scope>runtime</scope>
</dependency>

In questo modo, non è stato possibile fare riferimento alle classi log4j nel codice compilato ma sarà comunque possibile fare riferimento alle classi slf4j.
Se hai specificato le due dipendenze con l' compileora, nulla ti impedirà di fare riferimento alle classi log4j nel codice compilato e potresti quindi creare un accoppiamento indesiderato con l'implementazione del logging:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>...</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>...</version>
</dependency>

Un utilizzo comune runtimedell'ambito è la dichiarazione di dipendenza JDBC. Per scrivere codice portabile, non si desidera che il codice client faccia riferimento a classi della dipendenza DBMS specifica (ad esempio: dipendenza JDBC PostgreSQL) ma si desidera comunque includerlo nella propria applicazione poiché a runtime le classi sono necessarie per creare l'API JDBC funziona con questo DBMS.


0

In fase di compilazione abiliti i contratti / API che ti aspetti dalle tue dipendenze. (ad esempio: qui devi solo firmare un contratto con un provider di Internet a banda larga) In fase di esecuzione in realtà stai usando le dipendenze. (ad esempio: qui stai effettivamente utilizzando Internet a banda larga)


0

Per rispondere alla domanda "come può un programma non dipendere da qualcosa in fase di esecuzione da cui dipendeva durante la compilazione?", Diamo un'occhiata all'esempio di un processore di annotazioni.

Supponi di aver scritto il tuo processore di annotazioni e supponiamo che abbia una dipendenza in fase di compilazione da in com.google.auto.service:auto-servicemodo che possa usare @AutoService. Questa dipendenza è richiesta solo per compilare il processore di annotazione, ma non è richiesta in fase di esecuzione: tutti gli altri progetti che dipendono dal processore di annotazione per l'elaborazione delle annotazioni non richiedono la dipendenza da com.google.auto.service:auto-servicein fase di esecuzione (né in fase di compilazione né in qualsiasi altro momento) .

Questo non è molto comune, ma succede.


0

Lo runtimescopo è quello di impedire ai programmatori di aggiungere dipendenze dirette alle librerie di implementazione nel codice invece di usare astrazioni o facciate.

In altre parole, impone l'uso delle interfacce.

Esempi concreti:

1) Il tuo team sta usando SLF4J su Log4j. Vuoi che i tuoi programmatori utilizzino l'API SLF4J, non quella Log4j. Log4j deve essere utilizzato da SLF4J solo internamente. Soluzione:

  • Definire SLF4J come una normale dipendenza in fase di compilazione
  • Definisci log4j-core e log4j-api come dipendenze di runtime.

2) La tua applicazione sta accedendo a MySQL utilizzando JDBC. Volete che i vostri programmatori codifichino rispetto all'astrazione JDBC standard, non direttamente all'implementazione del driver MySQL.

  • Definisci mysql-connector-java(driver JDBC MySQL) come dipendenza di runtime.

Le dipendenze di runtime vengono nascoste durante la compilazione (generando errori in fase di compilazione se il codice ha una dipendenza "diretta" da esse) ma sono incluse durante il tempo di esecuzione e durante la creazione di artefatti distribuibili (file WAR, file jar SHADED, ecc.).

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.