Dépendance à la compilation et à l'exécution - Java

90

Quelle est la différence entre le moment de la compilation et les dépendances d'exécution en Java? Il est lié au chemin de classe, mais en quoi diffèrent-ils?

Kunal
la source

Réponses:

78
  • Dépendance au moment de la compilation : vous avez besoin de la dépendance dans votre CLASSPATHpour compiler votre artefact. Ils sont produits parce que vous avez une sorte de "référence" à la dépendance codée en dur dans votre code, comme l'appel newà une classe, l'extension ou l'implémentation de quelque chose (directement ou indirectement), ou un appel de méthode utilisant la reference.method()notation directe .

  • La dépendance d' exécution : Vous avez besoin de la dépendance dans votre CLASSPATHpour exécuter votre artefact. Ils sont produits parce que vous exécutez du code qui accède à la dépendance (soit de manière codée en dur, soit par réflexion ou autre).

Bien que la dépendance à la compilation implique généralement une dépendance à l'exécution, vous pouvez avoir une dépendance uniquement à la compilation. Ceci est basé sur le fait que Java lie uniquement les dépendances de classe lors du premier accès à cette classe, donc si vous n'accédez jamais à une classe particulière au moment de l'exécution car un chemin de code n'est jamais traversé, Java ignorera à la fois la classe et ses dépendances.

Exemple de ceci

Dans C.java (génère C.class):

package dependencies;
public class C { }

Dans A.java (génère 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());
        }
    }
}

Dans ce cas, A a une dépendance au moment de la compilation sur Cthrough B, mais il n'aura une dépendance à l'exécution sur C que si vous passez certains paramètres lors de l'exécution java dependencies.A, car la machine virtuelle Java essaiera uniquement de résoudre Bla dépendance de la Cdate d'exécution B b = new B(). Cette fonctionnalité vous permet de fournir au moment de l'exécution uniquement les dépendances des classes que vous utilisez dans vos chemins de code et d'ignorer les dépendances du reste des classes dans l'artefact.

gpeche
la source
1
Je sais que c'est maintenant une réponse très ancienne, mais comment la JVM peut-elle ne pas avoir C comme dépendance d'exécution dès le départ? S'il est capable de reconnaître "voici une référence à C, il est temps de l'ajouter en tant que dépendance", alors C n'est-il pas déjà essentiellement une dépendance puisque la JVM le reconnaît et sait où il se trouve?
wearebob
@wearebob Cela aurait pu être spécifié de cette façon, je suppose, mais ils ont décidé que la liaison paresseuse était meilleure, et personnellement je suis d'accord pour la raison indiquée ci-dessus: cela vous permet d'utiliser du code si nécessaire, mais ne vous oblige pas à l'inclure dans votre déploiement si vous n'en avez pas besoin. C'est très pratique lorsqu'il s'agit de code tiers.
gpeche
Si j'ai un jar déployé quelque part, il devra déjà contenir toutes ses dépendances. Il ne sait pas s'il sera exécuté avec des arguments ou non (donc il ne sait pas si C sera utilisé ou non), il devrait donc avoir C disponible de toute façon. Je ne vois tout simplement pas comment la mémoire / le temps sont économisés en n'ayant pas C sur le chemin de classe depuis le début.
wearebob
1
@wearebob un JAR n'a pas besoin d'inclure toutes ses dépendances. C'est pourquoi presque toutes les applications non triviales ont un répertoire / lib ou similaire contenant plusieurs JAR.
gpeche
33

Un exemple simple est de regarder une API comme l'API de servlet. Pour faire compiler vos servlets, vous avez besoin du servlet-api.jar, mais au moment de l'exécution, le conteneur de servlet fournit une implémentation de servlet api, vous n'avez donc pas besoin d'ajouter servlet-api.jar à votre chemin de classe d'exécution.

Martin Algesten
la source
Pour clarifier (cela m'a dérouté), si vous utilisez maven et construisez une guerre, "servlet-api" est généralement une dépendance "fournie" au lieu d'une dépendance "d'exécution", ce qui entraînerait son inclusion dans la guerre, si Je suis correct.
xdhmoore
2
«fourni» signifie, inclure au moment de la compilation, mais ne pas le regrouper dans le WAR ou dans une autre collection de dépendances. 'runtime' fait le contraire (non disponible à la compilation, mais fourni avec le WAR).
KC Baltz
29

Le compilateur a besoin du bon chemin de classe pour compiler les appels à une bibliothèque (dépendances au moment de la compilation)

La JVM a besoin du bon chemin de classe pour charger les classes dans la bibliothèque que vous appelez (dépendances d'exécution).

Ils peuvent être différents de plusieurs manières:

1) si votre classe C1 appelle la classe de bibliothèque L1 et que L1 appelle la classe de bibliothèque L2, alors C1 a une dépendance d'exécution sur L1 et L2, mais seulement une dépendance de compilation sur L1.

2) si votre classe C1 instancie dynamiquement une interface I1 à l'aide de Class.forName () ou d'un autre mécanisme, et que la classe d'implémentation de l'interface I1 est la classe L1, alors C1 a une dépendance d'exécution sur I1 et L1, mais seulement une dépendance de temps de compilation sur I1.

Autres dépendances "indirectes" qui sont les mêmes pour la compilation et l'exécution:

3) votre classe C1 étend la classe de bibliothèque L1, et L1 implémente l'interface I1 et étend la classe de bibliothèque L2: C1 a une dépendance à la compilation sur L1, L2 et I1.

4) votre classe C1 a une méthode foo(I1 i1)et une méthode bar(L1 l1)où I1 est une interface et L1 est une classe qui prend un paramètre qui est l'interface I1: C1 a une dépendance à la compilation sur I1 et L1.

Fondamentalement, pour faire quelque chose d'intéressant, votre classe doit s'interfacer avec d'autres classes et interfaces dans le chemin de classe. Le graphique de classe / interface formé par cet ensemble d' interfaces de bibliothèque donne la chaîne de dépendances à la compilation. Les implémentations de bibliothèque génèrent la chaîne de dépendances d'exécution. Notez que la chaîne de dépendances d'exécution dépend de l'exécution ou est lente: si l'implémentation de L1 dépend parfois de l'instanciation d'un objet de la classe L2, et que cette classe n'est instanciée que dans un scénario particulier, alors il n'y a pas de dépendance sauf dans ce scénario.

Jason S
la source
1
La dépendance de compilation dans l'exemple 1 ne devrait-elle pas être L1?
BalusC
Merci, mais comment fonctionne le chargement de la classe au moment de l'exécution? Au moment de la compilation, il est facile à comprendre. Mais au moment de l'exécution, comment agit-il, dans un cas où j'ai deux Jars de versions différentes? Lequel choisira-t-il?
Kunal
1
Je suis presque sûr que le chargeur de classe par défaut prend le chemin de classe et le parcourt dans l'ordre, donc si vous avez deux jars dans le chemin de classe qui contiennent tous les deux la même classe (par exemple com.example.fooutils.Foo), il utilisera celui qui est le premier dans le classpath. Soit cela, soit vous obtiendrez une erreur indiquant l'ambiguïté. Mais si vous voulez plus d'informations spécifiques aux chargeurs de classe, vous devez poser une question distincte.
Jason S
Je pense que dans le premier cas, les dépendances au moment de la compilation devraient également être présentes sur L2, c'est-à-dire que la phrase devrait être: 1) si votre classe C1 appelle la classe de bibliothèque L1 et que L1 appelle la classe de bibliothèque L2, alors C1 a une dépendance d'exécution sur L1 et L2, mais seulement une dépendance au moment de la compilation sur L1 et L2. Il en est ainsi, comme au moment de la compilation également lorsque le compilateur java vérifie L1, puis il vérifie également toutes les autres classes référencées par L1 (à l'exclusion des dépendances dynamiques comme Class.forName ("myclassname)) ... sinon comment vérifie-t-il que la compilation fonctionne bien. Veuillez expliquer si vous pensez le contraire
Rajesh Goel
1
Non. Vous devez vous renseigner sur le fonctionnement de la compilation et de la liaison en Java. Tout ce qui intéresse le compilateur, quand il fait référence à une classe externe, c'est comment utiliser cette classe, par exemple quelles sont ses méthodes et ses champs. Peu importe ce qui se passe réellement dans les méthodes de cette classe externe. Si L1 appelle L2, c'est un détail d'implémentation de L1, et L1 a déjà été compilé ailleurs.
Jason S
12

Java ne lie en fait rien au moment de la compilation. Il vérifie uniquement la syntaxe à l'aide des classes correspondantes qu'il trouve dans le CLASSPATH. Ce n'est qu'au moment de l'exécution que tout est assemblé et exécuté sur la base du CLASSPATH à ce moment-là.

JOTN
la source
Ce n'est pas avant le chargement ... le temps d'exécution est différent du temps de chargement.
surexchange
10

Les dépendances de compilation ne sont que les dépendances (autres classes) que vous utilisez directement dans la classe que vous compilez. Les dépendances d'exécution couvrent à la fois les dépendances directes et indirectes de la classe que vous exécutez. Ainsi, les dépendances d'exécution incluent les dépendances des dépendances et toutes les dépendances de réflexion telles que les noms de classe que vous avez dans a String, mais qui sont utilisés dans Class#forName().

BalusC
la source
Merci, mais comment fonctionne le chargement de la classe au moment de l'exécution? Au moment de la compilation, il est facile à comprendre. Mais au moment de l'exécution, comment agit-il, dans un cas où j'ai deux Jars de versions différentes? Quelle classe Class.forName () ramasserait-elle en cas de plusieurs classes de classes différentes dans un chemin de classe?
Kunal
Celui qui correspond au nom bien sûr. Si vous réellement dire « plusieurs versions d' une même classe », il dépend de la classloader. Le "plus proche" sera chargé.
BalusC
Eh bien, je pense que si vous avez A.jar avec A, B.jar avec B extends Aet C.jar avec C extends Balors C.jar dépend du temps de compilation sur A.jar même si la dépendance de C sur A est indirecte.
gpeche
1
Le problème dans toutes les dépendances au moment de la compilation est la dépendance d' interface (que l'interface soit via les méthodes d'une classe, ou via les méthodes d'une interface, ou via une méthode qui contient un argument qui est une classe ou une interface)
Jason S
2

Pour Java, la dépendance au moment de la compilation est la dépendance de votre code source. Par exemple, si la classe A appelle une méthode de la classe B, alors A dépend de B au moment de la compilation car A doit connaître B (type de B) pour être compilé. L'astuce ici devrait être la suivante: le code compilé n'est pas encore un code complet et exécutable. Il comprend des adresses remplaçables (symboles, métadonnées) pour les sources qui ne sont pas encore compilées ou qui existent dans des jars externes. Lors de la liaison, ces adresses doivent être remplacées par des adresses réelles dans la mémoire. Pour le faire correctement, des symboles / adresses corrects doivent être créés. Et cela peut être fait avec le type de classe (B). Je crois que c'est la principale dépendance au moment de la compilation.

La dépendance d'exécution est davantage liée au flux de contrôle réel. Il explore les adresses mémoire réelles. C'est une dépendance que vous avez lorsque votre programme est en cours d'exécution. Vous avez besoin de détails de classe B ici, comme des implémentations, pas seulement des informations de type. Si la classe n'existe pas, vous obtiendrez RuntimeException et JVM se fermera.

Les deux dépendances, généralement et ne devraient pas, circuler dans la même direction. C'est une question de conception OO.

En C ++, la compilation est un peu différente (pas juste à temps) mais elle a aussi un éditeur de liens. Donc, le processus pourrait être considéré comme similaire à Java, je suppose.

stdout
la source