Kann ein Programm während der Kompilierung von einer Bibliothek abhängen, aber nicht zur Laufzeit?

110

Ich verstehe den Unterschied zwischen Laufzeit und Kompilierungszeit und wie man zwischen beiden unterscheidet, aber ich sehe keine Notwendigkeit, zwischen Abhängigkeiten von Kompilierungszeit und Laufzeit zu unterscheiden .

Woran ich ersticke, ist Folgendes: Wie kann ein Programm zur Laufzeit nicht von etwas abhängen , von dem es während der Kompilierung abhängt? Wenn meine Java-App log4j verwendet, benötigt sie die Datei log4j.jar zum Kompilieren (mein Code integriert und ruft Mitgliedsmethoden aus log4j heraus auf) sowie zur Laufzeit (mein Code hat absolut keine Kontrolle darüber, was passiert, wenn Code in log4j vorhanden ist .jar wird ausgeführt).

Ich lese über Tools zur Abhängigkeitsauflösung wie Ivy und Maven, und diese Tools unterscheiden deutlich zwischen diesen beiden Arten von Abhängigkeiten. Ich verstehe die Notwendigkeit einfach nicht.

Kann jemand eine einfache Erklärung vom Typ "King's English" geben, vorzugsweise mit einem tatsächlichen Beispiel, das selbst ein armer Trottel wie ich verstehen könnte?

IAmYourFaja
quelle
2
Sie können Reflection verwenden und Klassen verwenden, die zum Zeitpunkt der Kompilierung nicht verfügbar waren. Denken Sie an "Plugin".
Per Alexandersson

Antworten:

64

Zur Laufzeit ist in der Regel eine Abhängigkeit zur Kompilierungszeit erforderlich. In maven compilewird dem Klassenpfad zur Laufzeit eine Abhängigkeit mit Gültigkeitsbereich hinzugefügt (z. B. werden sie in Kriegen nach WEB-INF / lib kopiert).

Es ist jedoch nicht unbedingt erforderlich; Zum Beispiel können wir gegen eine bestimmte API kompilieren, was sie zu einer Abhängigkeit zur Kompilierungszeit macht, aber zur Laufzeit eine Implementierung einschließen, die auch die API enthält.

Es kann Randfälle geben, in denen das Projekt eine bestimmte Abhängigkeit zum Kompilieren erfordert, der entsprechende Code jedoch nicht benötigt wird, diese sind jedoch selten.

Andererseits ist es sehr häufig, Laufzeitabhängigkeiten einzuschließen, die zur Kompilierungszeit nicht benötigt werden. Wenn Sie beispielsweise eine Java EE 6-Anwendung schreiben, kompilieren Sie mit der Java EE 6-API. Zur Laufzeit kann jedoch jeder Java EE-Container verwendet werden. Es ist dieser Container, der die Implementierung bereitstellt.

Abhängigkeiten zur Kompilierungszeit können durch Reflektion vermieden werden. Beispielsweise kann ein JDBC-Treiber mit a Class.forNamegeladen werden und die tatsächlich geladene Klasse kann über eine Konfigurationsdatei konfiguriert werden.

Artefakt
quelle
17
Über die Java EE-API - ist das nicht der Zweck des "bereitgestellten" Abhängigkeitsbereichs?
Kevin
15
Ein Beispiel, bei dem eine Abhängigkeit zum Kompilieren benötigt wird, aber zur Laufzeit nicht benötigt wird, ist lombok (www.projectlombok.org). Das JAR wird verwendet, um Java-Code zur Kompilierungszeit zu transformieren, wird jedoch zur Laufzeit überhaupt nicht benötigt. Wenn Sie den Bereich "bereitgestellt" angeben, wird das Glas nicht in den Krieg / das Glas aufgenommen.
Kevin
2
@ Kevin Ja, guter Punkt, der providedBereich fügt eine Abhängigkeit zur Kompilierungszeit hinzu, ohne eine Laufzeitabhängigkeit hinzuzufügen, in der Erwartung, dass die Abhängigkeit zur Laufzeit auf andere Weise bereitgestellt wird (z. B. eine gemeinsam genutzte Bibliothek im Container). runtimeAuf der anderen Seite wird eine Laufzeitabhängigkeit hinzugefügt, ohne dass dies zu einer Abhängigkeit zur Kompilierungszeit wird.
Artefacto
Kann man also mit Sicherheit sagen, dass es normalerweise eine 1: 1-Korrelation zwischen einer "Modulkonfiguration" (unter Verwendung von Ivy-Begriffen) und einem Hauptverzeichnis unter Ihrem Projektstamm gibt? Zum Beispiel befinden sich alle meine JUnit-Tests, die von der JUnit-JAR abhängen, unter dem Test / Root usw. Ich sehe nur nicht, wie dieselben Klassen, die unter demselben Quellstamm gepackt sind, so konfiguriert werden können, dass sie von unterschiedlichen abhängen JARs zu einem bestimmten Zeitpunkt. Wenn Sie log4j benötigen, benötigen Sie log4j. Es gibt keine Möglichkeit, denselben Code anzuweisen, log4j-Aufrufe unter 1 Konfiguration aufzurufen, aber log4j-Aufrufe unter einer Konfiguration ohne Protokollierung zu ignorieren, oder?
IAmYourFaja
30

Jede Maven-Abhängigkeit hat einen Bereich, der definiert, auf welchem ​​Klassenpfad diese Abhängigkeit verfügbar ist.

Wenn Sie eine JAR für ein Projekt erstellen, werden Abhängigkeiten nicht mit dem generierten Artefakt gebündelt. Sie werden nur zur Kompilierung verwendet. (Sie können Maven jedoch weiterhin dazu bringen, die Abhängigkeiten in das erstellte JAR aufzunehmen, siehe: Einschließen von Abhängigkeiten in ein JAR mit Maven )

Wenn Sie Maven zum Erstellen einer WAR- oder EAR-Datei verwenden, können Sie Maven so konfigurieren, dass Abhängigkeiten mit dem generierten Artefakt gebündelt werden, und Sie können es auch so konfigurieren, dass bestimmte Abhängigkeiten mithilfe des bereitgestellten Bereichs aus der WAR-Datei ausgeschlossen werden.

Der am häufigsten verwendete Bereich - Kompilierungsbereich - gibt an, dass die Abhängigkeit für Ihr Projekt vom Kompilierungsklassenpfad, den Unit-Test-Kompilierungs- und Ausführungsklassenpfaden und dem eventuellen Laufzeitklassenpfad verfügbar ist, wenn Sie Ihre Anwendung ausführen. In einer Java EE-Webanwendung bedeutet dies, dass die Abhängigkeit in Ihre bereitgestellte Anwendung kopiert wird. In einer JAR-Datei werden Abhängigkeiten jedoch nicht in den Kompilierungsbereich aufgenommen.

Laufzeitumfang gibt an, dass die Abhängigkeit für Ihr Projekt von den Klassenpfaden für die Ausführung von Komponententests und die Ausführung zur Laufzeit verfügbar ist. Im Gegensatz zum Kompilierungsbereich ist sie jedoch nicht verfügbar, wenn Sie Ihre Anwendung oder ihre Komponententests kompilieren . Eine Laufzeitabhängigkeit wird in Ihre bereitgestellte Anwendung kopiert, ist jedoch während der Kompilierung nicht verfügbar! Dies ist gut, um sicherzustellen, dass Sie nicht fälschlicherweise von einer bestimmten Bibliothek abhängig sind.

Schließlich gibt Provided Scope an, dass der Container, in dem Ihre Anwendung ausgeführt wird, die Abhängigkeit in Ihrem Namen bereitstellt. In einer Java EE-Anwendung bedeutet dies, dass sich die Abhängigkeit bereits im Klassenpfad des Servlet-Containers oder Anwendungsservers befindet und nicht in Ihre bereitgestellte Anwendung kopiert wird. Dies bedeutet auch, dass Sie diese Abhängigkeit zum Kompilieren Ihres Projekts benötigen.

Koray Tugay
quelle
@Koray Tugay Antwort ist präziser :) Ich habe eine kurze Frage, ob ich ein Abhängigkeitsglas mit dem Umfang der Laufzeit habe. Sucht der Maven zur Kompilierungszeit nach dem Glas?
gks
@gks Nein, es wird nicht in der Kompilierungszeit benötigt.
Koray Tugay
9

Sie benötigen zur Kompilierungszeit Abhängigkeiten, die Sie möglicherweise zur Laufzeit benötigen. Viele Bibliotheken werden jedoch ohne alle möglichen Abhängigkeiten ausgeführt. dh eine Bibliothek, die vier verschiedene XML-Bibliotheken verwenden kann, aber nur eine benötigt, um zu funktionieren.

Viele Bibliotheken benötigen wiederum andere Bibliotheken. Diese Bibliotheken werden zur Kompilierungszeit nicht benötigt, aber zur Laufzeit. dh wenn der Code tatsächlich ausgeführt wird.

Peter Lawrey
quelle
Können Sie uns Beispiele für solche Bibliotheken geben, die beim Kompilieren nicht benötigt werden, aber zur Laufzeit benötigt werden?
Cristiano
1
@Cristiano alle JDBC-Bibliotheken sind so. Auch Bibliotheken, die eine Standard-API implementieren.
Peter Lawrey
4

Im Allgemeinen haben Sie Recht und wahrscheinlich ist es die ideale Situation, wenn Laufzeit- und Kompilierungszeitabhängigkeiten identisch sind.

Ich werde Ihnen 2 Beispiele geben, wenn diese Regel falsch ist.

Wenn Klasse A von Klasse B abhängt, die von Klasse C abhängt, die von Klasse D abhängt, wobei A Ihre Klasse ist und B, C und D Klassen aus verschiedenen Bibliotheken von Drittanbietern sind, benötigen Sie zur Kompilierungszeit nur B und C und Sie benötigen auch D um Laufzeit. Oft verwenden Programme das dynamische Laden von Klassen. In diesem Fall benötigen Sie keine Klassen, die dynamisch von der Bibliothek geladen werden, die Sie zur Kompilierungszeit verwenden. Darüber hinaus wählt die Bibliothek häufig aus, welche Implementierung zur Laufzeit verwendet werden soll. Beispielsweise können SLF4J oder Commons Logging die Zielprotokollimplementierung zur Laufzeit ändern. Zum Kompilieren benötigen Sie nur SSL4J.

Gegenüberliegendes Beispiel, wenn Sie zur Kompilierungszeit mehr Abhängigkeiten benötigen als zur Laufzeit. Denken Sie, dass Sie eine Anwendung entwickeln, die in verschiedenen Umgebungen oder Betriebssystemen funktionieren muss. Sie benötigen alle plattformspezifischen Bibliotheken zur Kompilierungszeit und nur Bibliotheken, die zur Laufzeit für die aktuelle Umgebung benötigt werden.

Ich hoffe meine Erklärungen helfen.

AlexR
quelle
Können Sie in Ihrem Beispiel erläutern, warum C zur Kompilierungszeit benötigt wird? Ich habe den Eindruck (von stackoverflow.com/a/7257518/6095334 ), dass es davon abhängt , auf welche Methoden und Felder (von B) A verwiesen wird , ob C zur Kompilierungszeit benötigt wird oder nicht.
Hervian
3

Normalerweise ist das statische Abhängigkeitsdiagramm ein Unterdiagramm des dynamischen, siehe z. B. diesen Blogeintrag des Autors von NDepend .

Es gibt jedoch einige Ausnahmen, hauptsächlich Abhängigkeiten, die Compiler-Unterstützung hinzufügen, die zur Laufzeit unsichtbar wird. Zum Beispiel für die Codegenerierung über Lombok oder zusätzliche Prüfungen wie über das (steckbare Typ-) Checker Framework .

DaveFar
quelle
2

Ich bin gerade auf ein Problem gestoßen, das Ihre Frage beantwortet. servlet-api.jarist eine vorübergehende Abhängigkeit in meinem Webprojekt und wird sowohl zur Kompilierungszeit als auch zur Laufzeit benötigt. Aberservlet-api.jar aber auch in meiner Tomcat-Bibliothek enthalten.

Die Lösung besteht darin, servlet-api.jarin maven nur zur Kompilierungszeit verfügbar zu machen und nicht in meine Kriegsdatei zu packen, damit es nicht mit den servlet-api.jarin meiner Tomcat-Bibliothek enthaltenen kollidiert .

Ich hoffe, dies erklärt die Abhängigkeit von Kompilierungszeit und Laufzeit.

Mayoor
quelle
3
Ihr Beispiel ist für eine bestimmte Frage tatsächlich falsch, da es den Unterschied zwischen compileund providedBereichen und nicht zwischen compileund erklärt runtime. Compile scopewird sowohl zur Kompilierungszeit benötigt als auch in Ihrer App gepackt. Provided scopewird nur zur Kompilierungszeit benötigt, ist aber nicht in Ihrer App gepackt, da es von anderen Anbietern bereitgestellt wird, z. B. bereits auf dem Tomcat-Server.
MJar
1
Nun, ich denke , das ist ein ziemlich gutes Beispiel , weil die Frage war hinsichtlich der Kompilierung und Laufzeit Abhängigkeiten und nicht über compileund runtime maven Bereiche . Der providedBereich ist die Art und Weise, wie Maven den Fall behandelt, in dem eine Abhängigkeit zur Kompilierungszeit nicht im Laufzeitpaket enthalten sein sollte.
Christian Gawron
1

Ich verstehe den Unterschied zwischen Laufzeit und Kompilierungszeit und wie man zwischen beiden unterscheidet, aber ich sehe keine Notwendigkeit, zwischen Abhängigkeiten von Kompilierungszeit und Laufzeit zu unterscheiden.

Die allgemeinen Konzepte zur Kompilierungs- und Laufzeit sowie die Maven-spezifischen compileund runtimeBereichsabhängigkeiten sind zwei sehr unterschiedliche Dinge. Sie können sie nicht direkt vergleichen, da diese nicht denselben Rahmen haben: Die allgemeinen Kompilierungs- und Laufzeitkonzepte sind breit gefächert, während es bei den Maven- compileund runtimeScope-Konzepten speziell um die Verfügbarkeit / Sichtbarkeit von Abhängigkeiten je nach Zeit geht: Kompilierung oder Ausführung.
Vergessen Sie nicht, dass Maven vor allem ein javac/ javawrapper ist und dass Sie in Java einen Klassenpfad zur Kompilierungszeit haben, mit dem Sie angeben, javac -cp ... und einen Laufzeitklassenpfad, mit dem Sie angeben java -cp ....
Es wäre nicht falsch, den Maven- compileBereich als eine Möglichkeit zu betrachten, eine Abhängigkeit sowohl in der Java-Kompilierung als auch im Laufzeitklassenpfad hinzuzufügen (javacund java) während der Maven- runtimeBereich als eine Möglichkeit angesehen werden kann, eine Abhängigkeit nur im Java-Laufzeitklassenpfad ( javac) hinzuzufügen .

Woran ich ersticke, ist Folgendes: Wie kann ein Programm zur Laufzeit nicht von etwas abhängen, von dem es während der Kompilierung abhängt?

Was Sie beschreiben, hat keine Beziehung zu runtimeund compileUmfang.
Es sieht eher nach dem providedBereich aus, den Sie angeben, damit eine Abhängigkeit zur Kompilierungszeit, jedoch nicht zur Laufzeit davon abhängt.
Sie verwenden es, wenn Sie die Abhängigkeit zum Kompilieren benötigen, möchten es jedoch nicht in die gepackte Komponente (JAR, WAR oder andere) aufnehmen, da die Abhängigkeit bereits von der Umgebung bereitgestellt wird: Sie kann auf dem Server oder in einer anderen Komponente enthalten sein Pfad des Klassenpfads, der beim Starten der Java-Anwendung angegeben wurde.

Wenn meine Java-App log4j verwendet, benötigt sie die Datei log4j.jar zum Kompilieren (mein Code integriert und ruft Mitgliedsmethoden aus log4j heraus auf) sowie zur Laufzeit (mein Code hat absolut keine Kontrolle darüber, was passiert, wenn Code in log4j einmal vorhanden ist .jar wird ausgeführt).

In diesem Fall ja. Angenommen, Sie müssen einen tragbaren Code schreiben, der auf slf4j als Fassade vor log4j basiert, um später zu einer anderen Protokollierungsimplementierung wechseln zu können (log4J 2, logback oder eine andere).
In diesem Fall müssen Sie in Ihrem pom slf4j als compileAbhängigkeit angeben (dies ist die Standardeinstellung), aber Sie geben die log4j-Abhängigkeit als runtimeAbhängigkeit an:

<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>

Auf diese Weise konnten die log4j-Klassen im kompilierten Code nicht referenziert werden, Sie können jedoch weiterhin auf slf4j-Klassen verweisen.
Wenn Sie die beiden Abhängigkeiten mit der compileZeit angegeben haben, hindert Sie nichts daran, log4j-Klassen im kompilierten Code zu referenzieren, und Sie könnten so eine unerwünschte Kopplung mit der Protokollierungsimplementierung erstellen:

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

Eine häufige Verwendung des runtimeGültigkeitsbereichs ist die JDBC-Abhängigkeitsdeklaration. Um portablen Code zu schreiben, möchten Sie nicht, dass der Clientcode auf Klassen der spezifischen DBMS-Abhängigkeit verweist (z. B. PostgreSQL JDBC-Abhängigkeit), aber Sie möchten, dass er trotzdem in Ihre Anwendung aufgenommen wird, da zur Laufzeit die Klassen erstellt werden müssen Die JDBC-API funktioniert mit diesem DBMS.

davidxxx
quelle
0

Zur Kompilierungszeit aktivieren Sie Verträge / APIs, die Sie von Ihren Abhängigkeiten erwarten. (zB: hier unterschreiben Sie nur einen Vertrag mit dem Breitband-Internetprovider) Zur Laufzeit nutzen Sie tatsächlich die Abhängigkeiten. (zB: hier nutzen Sie tatsächlich das Breitband-Internet)

Nicolae Dascalu
quelle
0

Um die Frage zu beantworten, "wie kann ein Programm zur Laufzeit nicht von etwas abhängen, von dem es während der Kompilierung abhängt?", Schauen wir uns das Beispiel eines Annotationsprozessors an.

Angenommen, Sie haben Ihren eigenen Anmerkungsprozessor geschrieben und nehmen an com.google.auto.service:auto-service, dass er von der Kompilierungszeit abhängig ist, damit er verwendet werden kann @AutoService. Diese Abhängigkeit ist nur erforderlich , die Anmerkung Prozessor zu kompilieren, aber es ist nicht zur Laufzeit benötigt: alle anderen Projekte abhängig von Annotations - Prozessor für Anmerkungen Verarbeitung nicht nicht auf die Abhängigkeit benötigt com.google.auto.service:auto-servicezur Laufzeit (noch zur Compile-Zeit noch zu einem anderen Zeitpunkt) .

Dies ist nicht sehr häufig, aber es passiert.

user23288
quelle
0

Der runtimeBereich soll verhindern, dass Programmierer direkte Abhängigkeiten zu Implementierungsbibliotheken im Code hinzufügen, anstatt Abstraktionen oder Fassaden zu verwenden.

Mit anderen Worten, es wird die Verwendung von Schnittstellen erzwungen.

Konkrete Beispiele:

1) Ihr Team verwendet SLF4J über Log4j. Sie möchten, dass Ihre Programmierer die SLF4J-API verwenden, nicht die Log4j-API. Log4j darf nur intern von SLF4J verwendet werden. Lösung:

  • Definieren Sie SLF4J als reguläre Abhängigkeit zur Kompilierungszeit
  • Definieren Sie log4j-core und log4j-api als Laufzeitabhängigkeiten.

2) Ihre Anwendung greift über JDBC auf MySQL zu. Sie möchten, dass Ihre Programmierer gegen die Standard-JDBC-Abstraktion codieren, nicht direkt gegen die MySQL-Treiberimplementierung.

  • Definieren Sie mysql-connector-java(MySQL JDBC-Treiber) als Laufzeitabhängigkeit.

Laufzeitabhängigkeiten werden während der Kompilierung ausgeblendet (Fehler beim Kompilieren werden ausgelöst, wenn Ihr Code eine "direkte" Abhängigkeit von ihnen aufweist), werden jedoch während der Ausführungszeit und beim Erstellen von bereitstellbaren Artefakten (WAR-Dateien, SHADED-JAR-Dateien usw.) berücksichtigt.

Agustí Sánchez
quelle