Tempo di compilazione e dipendenza dal tempo di esecuzione - Java


90

Qual è la differenza tra il tempo di compilazione e le dipendenze del tempo di esecuzione in Java? È correlato al percorso di classe, ma in cosa differiscono?

Risposte:


78
  • In fase di compilazione delle dipendenze : è necessario la dipendenza nella vostra CLASSPATHper compilare il manufatto. Vengono prodotti perché si dispone di una sorta di "riferimento" alla dipendenza codificata nel codice, come la richiesta newdi una classe, l'estensione o l'implementazione di qualcosa (direttamente o indirettamente) o una chiamata al metodo che utilizza la reference.method()notazione diretta .

  • Dipendenza di runtime : è necessaria la dipendenza nel proprio CLASSPATHper eseguire il proprio artefatto. Vengono prodotti perché esegui codice che accede alla dipendenza (in modo hardcoded o tramite reflection o altro).

Sebbene la dipendenza in fase di compilazione in genere implichi una dipendenza in fase di esecuzione, è possibile avere una dipendenza solo in fase di compilazione. Ciò si basa sul fatto che Java collega solo le dipendenze della classe al primo accesso a quella classe, quindi se non si accede mai a una particolare classe in fase di esecuzione perché un percorso del codice non viene mai attraversato, Java ignorerà sia la classe che le sue dipendenze.

Esempio di questo

In C.java (genera C.class):

package dependencies;
public class C { }

In A.java (genera A.class):

package dependencies;
public class A {
    public static class B {
        public String toString() {
            C c = new C();
            return c.toString();
        }
    }
    public static void main(String[] args) {
        if (args.length > 0) {
            B b = new B();
            System.out.println(b.toString());
        }
    }
}

In questo caso, Aha una dipendenza in fase di compilazione da Cthrough B, ma avrà una dipendenza in fase di esecuzione da C solo se si passano alcuni parametri durante l'esecuzione java dependencies.A, poiché la JVM proverà a risolvere Bla dipendenza solo da Cquando deve essere eseguita B b = new B(). Questa funzionalità consente di fornire in fase di esecuzione solo le dipendenze delle classi utilizzate nei percorsi del codice e di ignorare le dipendenze del resto delle classi nell'artefatto.


1
So che questa è ora una risposta molto vecchia, ma come può la JVM non avere C come dipendenza di runtime dall'inizio? Se è in grado di riconoscere "ecco un riferimento a C, è ora di aggiungerlo come dipendenza", allora C non è già essenzialmente una dipendenza poiché la JVM lo riconosce e sa dove si trova?
wearebob

@wearebob Potrebbe essere stato specificato in questo modo immagino, ma hanno deciso che il collegamento pigro era migliore, e personalmente sono d'accordo per il motivo sopra indicato: ti consente di utilizzare del codice se necessario, ma non ti obbliga a includerlo in la tua distribuzione se non ne hai bisogno. Questo è abbastanza utile quando si ha a che fare con codice di terze parti.
gpeche

Se ho un jar distribuito da qualche parte, tuttavia, dovrà già contenere tutte le sue dipendenze. Non sa se verrà eseguito con argomenti o meno (quindi non sa se verrà utilizzato o meno C), quindi dovrebbe avere C disponibile in entrambi i casi. Semplicemente non vedo come viene salvata memoria / tempo non avendo C sul classpath dall'inizio.
wearebob

1
@wearebob un JAR non ha bisogno di includere tutte le sue dipendenze. Ecco perché quasi tutte le applicazioni non banali hanno una directory / lib o simile contenente più JAR.
gpeche

33

Un semplice esempio è guardare un'api come l'api servlet. Per compilare i servlet, è necessario servlet-api.jar, ma in fase di runtime il contenitore servlet fornisce un'implementazione api servlet quindi non è necessario aggiungere servlet-api.jar al percorso classe runtime.


Per chiarimenti (questo mi ha confuso), se stai usando Maven e costruisci una guerra, "servlet-api" è solitamente una dipendenza "fornita" invece di una dipendenza "runtime", il che lo farebbe essere incluso nella guerra, se Io sono corretto.
xdhmoore

2
"fornito" significa includere in fase di compilazione, ma non raggrupparlo in WAR o in altre raccolte di dipendenze. 'runtime' fa l'opposto (non disponibile in fase di compilazione, ma impacchettato con WAR).
KC Baltz

29

Il compilatore ha bisogno del percorso di classe corretto per compilare le chiamate a una libreria (compilare le dipendenze del tempo)

La JVM necessita del percorso classe corretto per caricare le classi nella libreria che si sta chiamando (dipendenze di runtime).

Possono essere diversi in un paio di modi:

1) se la tua classe C1 chiama la classe di libreria L1 e L1 chiama la classe di libreria L2, allora C1 ha una dipendenza di runtime da L1 e L2, ma solo una dipendenza del tempo di compilazione da L1.

2) se la tua classe C1 istanzia dinamicamente un'interfaccia I1 usando Class.forName () o qualche altro meccanismo e la classe di implementazione per l'interfaccia I1 è la classe L1, allora C1 ha una dipendenza di runtime da I1 e L1, ma solo una dipendenza dal tempo di compilazione su I1.

Altre dipendenze "indirette" che sono le stesse per la fase di compilazione e di esecuzione:

3) la classe C1 estende la classe di libreria L1 e L1 implementa l'interfaccia I1 ed estende la classe di libreria L2: C1 ha una dipendenza in fase di compilazione da L1, L2 e I1.

4) la tua classe C1 ha un metodo foo(I1 i1)e un metodo in bar(L1 l1)cui I1 è un'interfaccia e L1 è una classe che accetta un parametro che è l'interfaccia I1: C1 ha una dipendenza in fase di compilazione da I1 e L1.

Fondamentalmente, per fare qualcosa di interessante, la tua classe deve interfacciarsi con altre classi e interfacce nel classpath. Il grafico classe / interfaccia formato da quell'insieme di interfacce di libreria produce la catena di dipendenze in fase di compilazione. Le implementazioni della libreria producono la catena delle dipendenze di runtime. Si noti che la catena delle dipendenze di runtime dipende dal runtime o è lenta: se l'implementazione di L1 a volte dipende dall'istanza di un oggetto di classe L2 e quella classe viene istanziata solo in un particolare scenario, allora non c'è dipendenza tranne quello scenario.


1
La dipendenza in fase di compilazione nell'esempio 1 non dovrebbe essere L1?
BalusC

Grazie, ma come funziona il caricamento della classe in fase di esecuzione? In fase di compilazione è facile da capire. Ma in fase di esecuzione, come funziona, in un caso in cui ho due Jars di versioni diverse? Quale sceglierà?
Kunal

1
Sono abbastanza sicuro che il classloader predefinito prenda il classpath e lo passi in ordine, quindi se hai due jars nel classpath che contengono entrambi la stessa classe (ad es. Com.example.fooutils.Foo), utilizzerà quello che è il primo nel classpath. O quello o riceverai un errore che indica l'ambiguità. Ma se desideri maggiori informazioni specifiche per i classloader, dovresti porre una domanda separata.
Jason S

Penso che nel primo caso, le dipendenze del tempo di compilazione dovrebbero essere presenti anche su L2, ovvero la frase dovrebbe essere: 1) se la tua classe C1 chiama la classe di libreria L1 e L1 chiama la classe di libreria L2, allora C1 ha una dipendenza di runtime da L1 e L2, ma solo una dipendenza del tempo di compilazione da L1 e L2. È così, come in fase di compilazione anche quando il compilatore java verifica L1, quindi verifica anche tutte le altre classi referenziate da L1 (escluse le dipendenze dinamiche come Class.forName ("myclassname)) ... altrimenti come verifica che la compilazione funziona correttamente. Spiega se la pensi diversamente
Rajesh Goel

1
No. Devi leggere come funzionano la compilazione e il collegamento in Java. Tutto ciò che interessa al compilatore, quando si riferisce a una classe esterna, è come usare quella classe, ad esempio quali sono i suoi metodi e campi. Non gli importa cosa succede realmente nei metodi di quella classe esterna. Se L1 chiama L2, questo è un dettaglio di implementazione di L1 e L1 è già stato compilato altrove.
Jason S,

12

Java in realtà non collega nulla in fase di compilazione. Verifica solo la sintassi utilizzando le classi corrispondenti che trova in CLASSPATH. Non è fino al runtime che tutto viene messo insieme ed eseguito in base al CLASSPATH in quel momento.


Non è fino al momento del caricamento ... il runtime è diverso dal tempo di caricamento.
scambio eccessivo

10

Le dipendenze in fase di compilazione sono solo le dipendenze (altre classi) che usi direttamente nella classe che stai compilando. Le dipendenze di runtime coprono sia le dipendenze dirette che quelle indirette della classe in esecuzione. Pertanto, le dipendenze di runtime includono le dipendenze delle dipendenze e qualsiasi dipendenza di riflessione come i nomi di classe che hai in a String, ma sono usati in Class#forName().


Grazie, ma come funziona il caricamento della classe in fase di esecuzione? In fase di compilazione è facile da capire. Ma in fase di esecuzione, come funziona, in un caso in cui ho due Jars di versioni diverse? Quale classe riprenderebbe Class.forName () in caso di più classi di classi diverse in un percorso di classe?
Kunal

Quello che corrisponde al nome ovviamente. Se in realtà intendi "più versioni della stessa classe", dipende dal programma di caricamento classi. Verrà caricato quello "più vicino".
BalusC

Beh, penso che se hai A.jar con A, B.jar con B extends Ae C.jar con C extends Ballora C.jar dipende dal tempo di compilazione su A.jar anche se la dipendenza di C da A è indiretta.
gpeche

1
Il problema in tutte le dipendenze in fase di compilazione è la dipendenza dall'interfaccia (se l'interfaccia è attraverso i metodi di una classe, o attraverso i metodi di un'interfaccia, o attraverso un metodo che contiene un argomento che è una classe o un'interfaccia)
Jason S

2

Per Java, la dipendenza dal tempo di compilazione è la dipendenza del codice sorgente. Ad esempio, se la classe A chiama un metodo dalla classe B, allora A dipende da B al momento della compilazione poiché A deve conoscere B (tipo di B) per essere compilato. Il trucco qui dovrebbe essere questo: il codice compilato non è ancora un codice completo ed eseguibile. Include indirizzi sostituibili (simboli, metadati) per le fonti che non sono ancora compilate o esistenti in jar esterni. Durante il collegamento, questi indirizzi devono essere sostituiti da indirizzi effettivi in ​​memoria. Per farlo correttamente, è necessario creare simboli / indirizzi corretti. E questo può essere fatto con il tipo di classe (B). Credo che questa sia la principale dipendenza al momento della compilazione.

La dipendenza dal runtime è più correlata al flusso di controllo effettivo. Invoca indirizzi di memoria effettivi. È una dipendenza che hai quando il tuo programma è in esecuzione. Hai bisogno di dettagli di classe B qui come le implementazioni, non solo le informazioni sul tipo. Se la classe non esiste, riceverai RuntimeException e JVM uscirà.

Entrambe le dipendenze, generalmente e non dovrebbero, fluire nella stessa direzione. Tuttavia, questa è una questione di progettazione OO.

In C ++, la compilazione è leggermente diversa (non just-in-time) ma ha anche un linker. Quindi il processo potrebbe essere considerato simile a Java, immagino.

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.