web-dev-qa-db-fra.com

Un programme peut-il dépendre d'une bibliothèque pendant la compilation mais pas à l'exécution?

Je comprends la différence entre le temps d'exécution et le temps de compilation et comment différencier les deux, mais je ne vois pas la nécessité de faire la distinction entre le temps de compilation et le temps d'exécution dépendances.

Ce qui m'étouffe, c'est ceci: comment un programme ne peut-il pas dépendre de quelque chose au moment de l'exécution dont il dépend pendant la compilation? Si my Java app utilise log4j, il a besoin du fichier log4j.jar pour compiler (mon code intégrant et appelant des méthodes membres à partir de log4j) ainsi que de l'exécution (mon code a absolument aucun contrôle sur ce qui se passe une fois que le code dans log4j.jar est exécuté).

Je lis des outils de résolution de dépendances tels que Ivy et Maven, qui font clairement la distinction entre ces deux types de dépendances. Je ne comprends tout simplement pas la nécessité.

Quelqu'un peut-il donner une explication simple du type "King's English", de préférence avec un exemple concret que même une sève pauvre comme moi pourrait comprendre?

104
IAmYourFaja

Une dépendance à la compilation est généralement requise au moment de l'exécution. Dans maven, une dépendance compile scoped sera ajoutée au chemin de classe à l'exécution (par exemple, dans wars, elle sera copiée dans WEB-INF/lib).

Ce n'est cependant pas strictement requis; par exemple, nous pouvons compiler avec une certaine API, ce qui en fait une dépendance au moment de la compilation, mais incluons au moment de l'exécution une implémentation qui inclut également l'API.

Il peut y avoir des cas marginaux où le projet nécessite une certaine dépendance à la compilation mais où le code correspondant n'est pas réellement nécessaire, mais ceux-ci seront rares.

D'autre part, l'inclusion de dépendances d'exécution qui ne sont pas nécessaires à la compilation est très courante. Par exemple, si vous écrivez une application Java EE 6, vous compilez avec l'API Java EE 6, mais au moment de l'exécution, aucun Java EE peut être utilisé; c'est ce conteneur qui fournit l'implémentation.

Les dépendances à la compilation peuvent être évitées en utilisant la réflexion. Par exemple, un pilote JDBC peut être chargé avec un Class.forName et la classe réelle chargée peut être configurée via un fichier de configuration.

60
Artefacto

Chaque dépendance Maven a une portée qui définit le chemin d'accès aux classes sur lequel la dépendance est disponible.

Lorsque vous créez un fichier JAR pour un projet, les dépendances ne sont pas associées à l'artefact généré. ils ne sont utilisés que pour la compilation. (Cependant, vous pouvez toujours faire en sorte que maven inclue les dépendances dans le fichier jar construit, voir: y compris les dépendances dans un fichier jar avec Maven )

Lorsque vous utilisez Maven pour créer un fichier WAR ou EAR, vous pouvez configurer Maven pour regrouper les dépendances avec l'artefact généré. Vous pouvez également le configurer pour exclure certaines dépendances du fichier WAR à l'aide de la portée fournie.

La portée la plus courante - La portée de la compilation - indique que la dépendance est disponible pour votre projet sur le chemin de classe de compilation, les chemins de classe de compilation et d'exécution de test unitaire, ainsi que classpath d’éventuelle exécution lorsque vous exécutez votre application. Dans une application Web Java EE, cela signifie que la dépendance est copiée dans votre application déployée. Dans un fichier .jar, toutefois, les dépendances ne seront pas incluses dans la portée de la compilation.

Portée de l'exécution indique que la dépendance est disponible pour votre projet sur les chemins de classe d'exécution du test unitaire et d'exécution, mais contrairement à la portée de la compilation n'est pas disponible lorsque vous compilez votre application ou ses tests unitaires. Une dépendance d’exécution est copiée dans votre application déployée, mais elle n’est pas disponible lors de la compilation! Ceci est utile pour vous assurer de ne pas dépendre par erreur bibliothèque. (Voir par exemple: http://www.tugay.biz/2016/12/Apache-commons-logging-log4j-maven.html )

Enfin, Portée fournie indique que le conteneur dans lequel votre application s'exécute fournit la dépendance en votre nom. Dans une Java EE, cela signifie que la dépendance existe déjà sur le chemin d'accès aux classes du conteneur Servlet ou du serveur d'applications et que n'est pas copié dans votre application déployée. Cela signifie également que vous avez besoin de cette dépendance pour compiler votre projet.

27
Koray Tugay

Vous avez besoin au moment de la compilation des dépendances dont vous pourriez avoir besoin au moment de l'exécution. Cependant, de nombreuses bibliothèques fonctionnent sans toutes ses dépendances possibles. c'est-à-dire une bibliothèque qui peut utiliser quatre bibliothèques XML différentes, mais n'en a besoin que d'une.

De nombreuses bibliothèques ont besoin d’autres bibliothèques à leur tour. Ces bibliothèques ne sont pas nécessaires à la compilation, mais au moment de l'exécution. c'est-à-dire quand le code est réellement exécuté.

9
Peter Lawrey

En général, vous avez raison et c'est probablement la situation idéale si les dépendances d'exécution et de compilation sont identiques.

Je vais vous donner 2 exemple lorsque cette règle est incorrecte.

Si la classe A dépend de la classe B, la classe C dépend de la classe D, où A est votre classe et B, C et D sont des classes de bibliothèques tierces différentes. Vous n'avez besoin que de B et C au moment de la compilation et de D en tant que runtime. Les programmes utilisent souvent un chargement de classe dynamique. Dans ce cas, vous n'avez pas besoin des classes chargées dynamiquement par la bibliothèque que vous utilisez au moment de la compilation. De plus, la bibliothèque choisit souvent l'implémentation à utiliser lors de l'exécution. Par exemple, SLF4J ou Commons Logging peut modifier l'implémentation du journal cible lors de l'exécution. Vous n'avez besoin que de SSL4J lui-même au moment de la compilation.

Exemple opposé lorsque vous avez besoin de davantage de dépendances au moment de la compilation qu’au moment de l’exécution. Pensez que vous développez une application devant fonctionner sur différents environnements ou systèmes d'exploitation. Vous avez besoin de toutes les bibliothèques spécifiques à la plate-forme au moment de la compilation et uniquement des bibliothèques nécessaires pour l'environnement actuel au moment de l'exécution.

J'espère que mes explications aideront.

4
AlexR

Habituellement, le graphe des dépendances statiques est un sous-graphe du graphe dynamique, voir par exemple. cette entrée de blog de l'auteur de NDepend .

Cela dit, il existe quelques exceptions, principalement des dépendances qui ajoutent le support du compilateur, qui devient invisible au moment de l'exécution. Par exemple, pour la génération de code via Lombok ou des contrôles supplémentaires via le (Pluggable Type-) Checker Framework .

3
DaveFar

Je viens de rencontrer un problème qui répond à votre question. servlet-api.jar est une dépendance transitoire dans mon projet Web et est nécessaire à la fois lors de la compilation et de l'exécution. Mais servlet-api.jar est également inclus dans ma bibliothèque Tomcat.

La solution ici est de faire servlet-api.jar in maven disponible uniquement au moment de la compilation et non empaqueté dans mon fichier war afin qu’il ne soit pas en conflit avec le servlet-api.jar contenu dans ma bibliothèque Tomcat.

J'espère que cela explique le temps de compilation et la dépendance à l'exécution.

2
Mayoor

Je comprends la différence entre le temps d'exécution et le temps de compilation et comment différencier les deux, mais je ne vois pas la nécessité de faire la distinction entre les dépendances au moment de la compilation et à l'exécution.

Les concepts généraux de compilation et d'exécution, ainsi que les dépendances de portée propres à Maven compile et runtime sont deux choses très différentes. Vous ne pouvez pas les comparer directement car ils n’ont pas le même cadre: les concepts généraux de compilation et d’exécution sont larges tandis que les concepts de portée maven compile et runtime concernent spécifiquement les dépendances disponibilité/visibilité selon à l'époque: compilation ou exécution.
N'oubliez pas que Maven est avant tout un wrapper javac/Java et que dans Java vous avez un classpath de temps de compilation que vous spécifier avec javac -cp ... et un classpath d’exécution que vous spécifiez avec Java -cp ....
Il ne serait pas faux de considérer la portée de Maven compile comme un moyen d’ajouter une dépendance à la fois dans la compilation Java et la classe d’exécution (javac et Java) tandis que la portée de Maven runtime peut être vue comme un moyen d’ajouter une dépendance uniquement dans le Java classppath (javac).

Ce qui m'étouffe, c'est ceci: comment un programme ne peut-il pas dépendre de quelque chose à l'exécution dont il dépend pendant la compilation?

Ce que vous décrivez n'a aucun lien avec les portées runtime et compile.
Cela ressemble davantage à la portée provided que vous spécifiez pour qu'une dépendance en dépende au moment de la compilation, mais pas au moment de l'exécution.
Vous l’utilisez car vous avez besoin de la dépendance pour la compilation, mais vous ne souhaitez pas l’inclure dans le composant empaqueté (JAR, WAR ou autre), car la dépendance est déjà fournie par l'environnement: il peut être inclus dans le serveur ou n'importe quel chemin du classpath spécifié en tant qu'application Java est démarré.

Si my Java app utilise log4j, il a besoin du fichier log4j.jar pour compiler (mon code intégrant et appelant des méthodes membres à partir de log4j) ainsi que de l'exécution (mon code a absolument aucun contrôle sur ce qui se passe une fois que le code dans log4j.jar est exécuté).

Dans ce cas oui. Mais supposons que vous ayez besoin d'écrire un code portable qui s'appuie sur slf4j comme façade devant log4j pour pouvoir passer ultérieurement à une autre implémentation de journalisation (log4J 2, logback ou toute autre).
Dans ce cas, vous devez spécifier slf4j en tant que dépendance compile (c'est la valeur par défaut) mais vous spécifierez la dépendance log4j en tant que dépendance runtime:

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

De cette manière, les classes log4j ne pourraient pas être référencées dans le code compilé mais vous pourrez toujours faire référence à des classes slf4j.
Si vous avez spécifié les deux dépendances avec l'heure compile, rien ne vous empêchera de référencer les classes log4j dans le code compilé et vous pourriez ainsi créer un couplage indésirable avec l'implémentation de journalisation:

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

Une utilisation courante de la portée runtime est la déclaration de dépendance JDBC. Pour écrire du code portable, vous ne voulez pas que le code client puisse faire référence à des classes de la dépendance spécifique au SGBD (par exemple: dépendance PostgreSQL JDBC), mais vous souhaitez tout de même l'inclure dans votre application car, au moment de l'exécution, les classes doivent l'API JDBC fonctionne avec ce SGBD.

1
davidxxx

Au moment de la compilation, vous activez les contrats/API que vous attendez de vos dépendances. (par exemple: ici, vous venez de signer un contrat avec le fournisseur d'accès Internet à large bande) Au moment de l'exécution, vous utilisez les dépendances. (par exemple: ici vous utilisez réellement l'Internet à haut débit)

0
Nicolae Dascalu

Pour répondre à la question "Comment un programme peut-il ne pas dépendre de quelque chose au moment de l'exécution qui en dépend pendant la compilation?", Examinons l'exemple d'un processeur d'annotation.

Supposons que vous avez écrit votre propre processeur d'annotation et supposez qu'il ait une dépendance à la compilation, aucom.google.auto.service:auto-service pour qu'il puisse utiliser @AutoService. Cette dépendance n’est nécessaire que pour compiler le processeur d’annotation, mais elle n’est pas requise au moment de l’exécution: tous les autres projets dépendant de votre processeur d’annotation pour le traitement des annotations ne pas nécessitent la dépendance de com.google.auto.service:auto-service à l'exécution (ni à la compilation ni à aucun autre moment).

Ce n'est pas très commun, mais ça arrive.

0
user23288