Kompilierungszeit vs. Laufzeitabhängigkeit - Java

88

Was ist der Unterschied zwischen Kompilierungs- und Laufzeitabhängigkeiten in Java? Es hängt mit dem Klassenpfad zusammen, aber wie unterscheiden sie sich?

Kunal
quelle

Antworten:

75
  • Compile-Zeitabhängigkeit : Sie müssen die Abhängigkeit in Ihrem CLASSPATHIhren Artefakt zu kompilieren. Sie werden erzeugt, weil Sie eine Art "Verweis" auf die in Ihrem Code fest codierte Abhängigkeit haben, z. B. das Aufrufen neweiner Klasse, das Erweitern oder Implementieren von etwas (entweder direkt oder indirekt) oder einen Methodenaufruf unter Verwendung der direkten reference.method()Notation.

  • Laufzeitabhängigkeit : Sie benötigen die Abhängigkeit in Ihrem CLASSPATH, um Ihr Artefakt auszuführen. Sie werden erzeugt, weil Sie Code ausführen, der auf die Abhängigkeit zugreift (entweder fest codiert oder durch Reflektion oder was auch immer).

Obwohl die Abhängigkeit zur Kompilierungszeit normalerweise eine Laufzeitabhängigkeit impliziert, können Sie nur eine Abhängigkeit zur Kompilierungszeit haben. Dies basiert auf der Tatsache, dass Java Klassenabhängigkeiten nur beim ersten Zugriff auf diese Klasse verknüpft. Wenn Sie also zur Laufzeit nie auf eine bestimmte Klasse zugreifen, weil ein Codepfad nie durchlaufen wird, ignoriert Java sowohl die Klasse als auch ihre Abhängigkeiten.

Beispiel dafür

In C.java (erzeugt C.class):

package dependencies;
public class C { }

In A.java (erzeugt 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 diesem Fall Abesteht eine Abhängigkeit von der Kompilierungszeit von Cbis B, aber nur eine Laufzeitabhängigkeit von C, wenn Sie bei der Ausführung einige Parameter übergeben java dependencies.A, da die JVM nur versucht, die BAbhängigkeit von Cder Ausführung zu lösen , wenn sie ausgeführt werden soll B b = new B(). Mit dieser Funktion können Sie zur Laufzeit nur die Abhängigkeiten der Klassen bereitstellen, die Sie in Ihren Codepfaden verwenden, und die Abhängigkeiten der übrigen Klassen im Artefakt ignorieren.

gpeche
quelle
1
Ich weiß, dass dies jetzt eine sehr alte Antwort ist, aber wie kann die JVM nicht von Anfang an C als Laufzeitabhängigkeit haben? Wenn es erkennen kann, dass "hier ein Verweis auf C ist, Zeit, ihn als Abhängigkeit hinzuzufügen", ist C dann nicht bereits im Wesentlichen eine Abhängigkeit, da die JVM sie erkennt und weiß, wo sie sich befindet?
Wearebob
@wearebob Es hätte so angegeben werden können, aber sie haben entschieden, dass Lazy Linking besser ist, und ich persönlich stimme aus dem oben genannten Grund zu: Es ermöglicht Ihnen, bei Bedarf Code zu verwenden, zwingt Sie jedoch nicht, ihn aufzunehmen Ihre Bereitstellung, wenn Sie sie nicht benötigen. Das ist sehr praktisch, wenn Sie mit Code von Drittanbietern arbeiten.
Gpeche
Wenn ich ein Glas irgendwo bereitgestellt habe, muss es bereits alle Abhängigkeiten enthalten. Es weiß nicht, ob es mit Argumenten ausgeführt wird oder nicht (es weiß also nicht, ob C verwendet wird oder nicht), daher müsste C so oder so verfügbar sein. Ich sehe nur nicht, wie Speicher / Zeit gespart wird, wenn C von Anfang an nicht im Klassenpfad ist.
Wearebob
1
@wearebob Eine JAR muss nicht alle ihre Abhängigkeiten enthalten. Aus diesem Grund verfügt fast jede nicht triviale Anwendung über ein / lib-Verzeichnis oder ähnliches, das mehrere JARs enthält.
Gpeche
32

Ein einfaches Beispiel ist das Betrachten einer API wie der Servlet-API. Damit Ihre Servlets kompiliert werden können, benötigen Sie die Datei servlet-api.jar. Zur Laufzeit bietet der Servlet-Container jedoch eine Servlet-API-Implementierung, sodass Sie servlet-api.jar nicht zu Ihrem Laufzeitklassenpfad hinzufügen müssen.

Martin Algesten
quelle
Zur Verdeutlichung (das hat mich verwirrt), wenn Sie Maven verwenden und einen Krieg aufbauen, ist "Servlet-API" normalerweise eine "bereitgestellte" Abhängigkeit anstelle einer "Laufzeit" -Abhängigkeit, die dazu führen würde, dass sie in den Krieg aufgenommen wird, wenn Ich bin richtig.
xdhmoore
2
'bereitgestellt' bedeutet, zur Kompilierungszeit einschließen, aber nicht in WAR oder einer anderen Sammlung von Abhängigkeiten bündeln. 'runtime' macht das Gegenteil (beim Kompilieren nicht verfügbar, aber mit WAR gepackt).
KC Baltz
29

Der Compiler benötigt den richtigen Klassenpfad, um Aufrufe an eine Bibliothek zu kompilieren (Abhängigkeiten zur Kompilierungszeit).

Die JVM benötigt den richtigen Klassenpfad, um die Klassen in die aufgerufene Bibliothek zu laden (Laufzeitabhängigkeiten).

Sie können in vielerlei Hinsicht unterschiedlich sein:

1) Wenn Ihre Klasse C1 die Bibliotheksklasse L1 und L1 die Bibliotheksklasse L2 aufruft, hat C1 eine Laufzeitabhängigkeit von L1 und L2, aber nur eine Kompilierungszeitabhängigkeit von L1.

2) Wenn Ihre Klasse C1 eine Schnittstelle I1 mithilfe von Class.forName () oder einem anderen Mechanismus dynamisch instanziiert und die implementierende Klasse für die Schnittstelle I1 die Klasse L1 ist, hat C1 eine Laufzeitabhängigkeit von I1 und L1, jedoch nur eine Abhängigkeit von der Kompilierungszeit auf I1.

Andere "indirekte" Abhängigkeiten, die für die Kompilierungs- und Laufzeit gleich sind:

3) Ihre Klasse C1 erweitert die Bibliotheksklasse L1, und L1 implementiert die Schnittstelle I1 und erweitert die Bibliotheksklasse L2: C1 ist zur Kompilierungszeit von L1, L2 und I1 abhängig.

4) Ihre Klasse C1 hat eine Methode foo(I1 i1)und eine Methode, bar(L1 l1)wobei I1 eine Schnittstelle ist und L1 eine Klasse ist, die einen Parameter annimmt, der Schnittstelle I1 ist: C1 hat eine Abhängigkeit zur Kompilierungszeit von I1 und L1.

Grundsätzlich muss Ihre Klasse mit anderen Klassen und Schnittstellen im Klassenpfad kommunizieren, um etwas Interessantes zu tun. Die Klasse / Schnittstellen Graph , der durch diesen Satz von Bibliothek gebildet Schnittstellen ergibt die Kompilierung-Abhängigkeitskette. Die Bibliothek Implementierungen ergeben die Laufzeitabhängigkeitskette. Beachten Sie, dass die Laufzeitabhängigkeitskette zur Laufzeit abhängig oder ausfallsicher ist: Wenn die Implementierung von L1 manchmal von der Instanziierung eines Objekts der Klasse L2 abhängt und diese Klasse nur in einem bestimmten Szenario instanziiert wird, gibt es keine Abhängigkeit außer in dieses Szenario.

Jason S.
quelle
1
Sollte die Abhängigkeit zur Kompilierungszeit in Beispiel 1 nicht L1 sein?
BalusC
Danke, aber wie funktioniert das Laden der Klasse zur Laufzeit? Zur Kompilierungszeit ist es leicht zu verstehen. Aber wie verhält es sich zur Laufzeit, wenn ich zwei Gläser mit unterschiedlichen Versionen habe? Welches wird es auswählen?
Kunal
1
Ich bin mir ziemlich sicher, dass der Standardklassenlader den Klassenpfad verwendet und ihn der Reihe nach durchläuft. Wenn Sie also zwei Jars im Klassenpfad haben, die beide dieselbe Klasse enthalten (z. B. com.example.fooutils.Foo), wird derjenige verwendet, der ist zuerst im Klassenpfad. Entweder das, oder Sie erhalten eine Fehlermeldung, die die Mehrdeutigkeit angibt. Wenn Sie jedoch weitere Informationen zu Klassenladern wünschen, sollten Sie eine separate Frage stellen.
Jason S
Ich denke, im ersten Fall sollten die Abhängigkeiten zur Kompilierungszeit auch von L2 vorhanden sein, dh der Satz sollte lauten: 1) Wenn Ihre Klasse C1 die Bibliotheksklasse L1 aufruft und L1 die Bibliotheksklasse L2 aufruft, hat C1 eine Laufzeitabhängigkeit von L1 und L2, aber nur eine Kompilierungszeitabhängigkeit von L1 & L2. Dies ist so, wie zur Kompilierungszeit auch, wenn der Java-Compiler L1 überprüft, dann überprüft er auch alle anderen Klassen, auf die L1 verweist (mit Ausnahme der dynamischen Abhängigkeiten wie Class.forName ("myclassname)) ... andernfalls, wie dies überprüft wird Die Zusammenstellung funktioniert gut. Bitte erklären Sie, wenn Sie anders denken
Rajesh Goel
1
Nein. Sie müssen sich über die Funktionsweise der Kompilierung und Verknüpfung in Java informieren. Alle der Compiler kümmert sich um, wenn es bezieht sich auf eine externe Klasse, ist , wie man verwenden diese Klasse, zum Beispiel , was seine Methoden und Felder sind. Es ist egal, was tatsächlich in den Methoden dieser externen Klasse passiert. Wenn L1 L2 aufruft, ist dies ein Implementierungsdetail von L1, und L1 wurde bereits an anderer Stelle kompiliert.
Jason S
12

Java verknüpft zur Kompilierungszeit eigentlich nichts. Die Syntax wird nur anhand der übereinstimmenden Klassen überprüft, die im CLASSPATH gefunden werden. Erst zur Laufzeit wird alles basierend auf dem CLASSPATH zu diesem Zeitpunkt zusammengestellt und ausgeführt.

JOTN
quelle
Es ist nicht bis zur Ladezeit ... Laufzeit unterscheidet sich von Ladezeit.
Überaustausch
10

Kompilierzeitabhängigkeiten sind nur die Abhängigkeiten (andere Klassen), die Sie direkt in der Klasse verwenden, die Sie kompilieren. Laufzeitabhängigkeiten decken sowohl die direkten als auch die indirekten Abhängigkeiten der Klasse ab, die Sie ausführen. Daher umfassen Laufzeitabhängigkeiten Abhängigkeiten von Abhängigkeiten und alle Reflexionsabhängigkeiten wie Klassennamen, die Sie in a haben, in denen Sie Stringjedoch verwendet werden Class#forName().

BalusC
quelle
Danke, aber wie funktioniert das Laden der Klasse zur Laufzeit? Zur Kompilierungszeit ist es leicht zu verstehen. Aber wie verhält es sich zur Laufzeit, wenn ich zwei Gläser mit unterschiedlichen Versionen habe? Welche Klasse würde Class.forName () bei mehreren Klassen verschiedener Klassen in einem Klassenpfad abholen?
Kunal
Der, der natürlich zum Namen passt. Wenn Sie tatsächlich "mehrere Versionen derselben Klasse" meinen, hängt dies vom Klassenladeprogramm ab. Der "nächste" wird geladen.
BalusC
Nun, ich denke, wenn Sie A.jar mit A, B.jar mit B extends Aund C.jar mit haben, C extends Bdann hängt C.jar von der Kompilierungszeit von A.jar ab, obwohl die C-Abhängigkeit von A indirekt ist.
Gpeche
1
Das Problem bei allen Abhängigkeiten zur Kompilierungszeit ist die Schnittstellenabhängigkeit (unabhängig davon, ob die Schnittstelle über die Methoden einer Klasse oder über die Methoden einer Schnittstelle oder über eine Methode erfolgt, die ein Argument enthält, das eine Klasse oder Schnittstelle ist)
Jason S
2

Für Java ist die Abhängigkeit von der Kompilierungszeit die Abhängigkeit Ihres Quellcodes. Wenn Klasse A beispielsweise eine Methode aus Klasse B aufruft, ist A zur Kompilierungszeit von B abhängig, da A über B (Typ von B) Bescheid wissen muss, um kompiliert zu werden. Der Trick hier sollte folgender sein: Kompilierter Code ist noch kein vollständiger und ausführbarer Code. Es enthält austauschbare Adressen (Symbole, Metadaten) für die Quellen, die noch nicht kompiliert wurden oder in externen Gläsern vorhanden sind. Während der Verknüpfung müssen diese Adressen durch tatsächliche Adressen im Speicher ersetzt werden. Um dies richtig zu machen, sollten korrekte Symbole / Adressen erstellt werden. Und dies kann mit dem Typ der Klasse (B) erfolgen. Ich glaube, das ist die Hauptabhängigkeit beim Kompilieren.

Die Laufzeitabhängigkeit hängt eher mit dem tatsächlichen Kontrollfluss zusammen. Es handelt sich um tatsächliche Speicheradressen. Es ist eine Abhängigkeit, die Sie haben, wenn Ihr Programm ausgeführt wird. Sie benötigen hier Details der Klasse B wie Implementierungen, nicht nur die Typinformationen. Wenn die Klasse nicht vorhanden ist, erhalten Sie RuntimeException und JVM wird beendet.

Beide Abhängigkeiten fließen im Allgemeinen und sollten nicht in die gleiche Richtung. Dies ist jedoch eine Frage des OO-Designs.

In C ++ ist die Kompilierung etwas anders (nicht nur in der Zeit), aber es gibt auch einen Linker. Der Prozess könnte also ähnlich wie Java sein, denke ich.

stdout
quelle