Bonsoir, chers collègues. Il y a exactement un mois, nous avons reçu un contrat pour la traduction de
Java moderne de Manning, qui devrait être l'un de nos nouveaux produits les plus remarquables l'année prochaine. Le problème de "Modern" et "Legacy" en Java est si aigu que le besoin d'un tel livre est assez mûr. L'ampleur de la catastrophe et la manière de résoudre les problèmes dans Java 9 sont brièvement décrites dans un article de Wayne Citrin, dont nous voulons vous proposer une traduction aujourd'hui.
Toutes les quelques années, avec la sortie d'une nouvelle version de Java, les intervenants de JavaOne commencent à savourer de nouvelles constructions de langage et API et louent leurs vertus. Et les développeurs zélés, quant à eux, sont impatients d'introduire de nouvelles fonctionnalités. Une telle image est loin de la réalité - elle ne tient absolument pas compte du fait que la plupart des programmeurs sont occupés à prendre en
charge et à finaliser les applications existantes , et à ne pas écrire de nouvelles applications à partir de zéro.
La plupart des applications - en particulier les applications commerciales - doivent être rétrocompatibles avec les versions antérieures de Java qui ne prennent pas en charge toutes ces nouvelles fonctionnalités de super-duper. Enfin, la plupart des clients et des utilisateurs finaux, en particulier dans le segment des grandes entreprises, se méfient d'une mise à niveau radicale de la plate-forme Java, préférant attendre qu'elle se renforce.
Par conséquent, dès que le développeur va tenter une nouvelle opportunité, il est confronté à des problèmes. Souhaitez-vous utiliser les méthodes d'interface par défaut dans votre code? Peut-être - si vous avez de la chance et que votre application n'a pas besoin d'interagir avec Java 7 ou une version antérieure. Vous souhaitez utiliser la classe
java.util.concurrent.ThreadLocalRandom
pour générer des nombres pseudo-aléatoires dans une application multithread? Cela ne fonctionnera pas si votre application doit s'exécuter sur Java 6, 7, 8 ou 9 en même temps.
Avec la sortie de la nouvelle version, les développeurs qui prennent en charge le code hérité se sentent comme des enfants forcés de regarder une vitrine de pâtisserie. Ils ne sont pas autorisés à l'intérieur, leur destin est donc la déception et la frustration.
Alors, y a-t-il quelque chose dans la nouvelle version de Java 9 pour les programmeurs impliqués dans la prise en charge du code hérité? Quelque chose qui pourrait leur faciliter la vie? Heureusement oui.
Ce qui devait être fait avec le support du code hérité, c'est l'apparition de Java 9Bien sûr, vous pouvez pousser les capacités de la nouvelle plate-forme dans des
applications héritées dans lesquelles vous devez vous conformer à la compatibilité descendante. En particulier, il existe toujours des opportunités pour profiter des nouvelles API. Cependant, cela peut s'avérer un peu moche.
Par exemple, vous pouvez appliquer une liaison tardive si vous souhaitez accéder à la nouvelle API lorsque votre application doit également fonctionner avec des versions plus anciennes de Java qui ne prennent pas en charge cette API. Supposons que vous souhaitiez utiliser la classe
java.util.stream.LongStream
, introduite dans Java 8, et que vous souhaitiez utiliser la
anyMatch(LongPredicate)
de cette classe, mais que l'application doit être compatible avec Java 7. Vous pouvez créer une classe d'assistance, comme ceci:
public classLongStreamHelper { private static Class longStreamClass; private static Class longPredicateClass; private static Method anyMatchMethod; static { try { longStreamClass = Class.forName("java.util.stream.LongStream"); longPredicateClass = Class.forName("java.util.function.LongPredicate"); anyMatchMethod = longStreamClass.getMethod("anyMatch", longPredicateClass): } catch (ClassNotFoundException e) { longStreamClass = null; longPredicateClass = null; anyMatchMethod = null } catch (NoSuchMethodException e) { longStreamClass = null; longPredicateClass = null; anyMatchMethod = null; } public static boolean anyMatch(Object theLongStream, Object thePredicate) throws NotImplementedException { if (longStreamClass == null) throw new NotImplementedException(); try { Boolean result = (Boolean) anyMatchMethod.invoke(theLongStream, thePredicate); return result.booleanValue(); } catch (Throwable e) {
Il existe des moyens de simplifier cette opération, ou de la rendre plus générale ou plus efficace - vous avez l'idée.
Au lieu d'appeler
theLongStream.anyMatch(thePredicate)
, comme vous le feriez dans Java 8, vous pouvez appeler
LongStreamHelper.anyMatch(theLongStream, thePredicate)
dans n'importe quelle version de Java. Si vous traitez avec Java 8, cela fonctionnera, mais si avec Java 7, le programme
NotImplementedException
une
NotImplementedException
.
Pourquoi est-ce laid? Parce que le code peut devenir trop compliqué si vous devez accéder à de nombreuses API (en fait, même maintenant, avec une seule API, cela est déjà gênant). En outre, cette pratique n'est pas sécurisée, car le code ne peut pas mentionner directement
LongStream
ou
LongPredicate
. Enfin, cette pratique est beaucoup moins efficace, en raison de la surcharge de réflexion, ainsi que des blocs
try-catch
supplémentaires. Par conséquent, bien que cela puisse être fait de cette façon, ce n'est pas trop intéressant et il est lourd d'erreurs dues à la négligence.
Oui, vous pouvez accéder aux nouvelles API et votre code conserve en même temps la compatibilité descendante, mais vous ne réussirez pas avec les nouvelles constructions de langage. Par exemple, supposons que nous devons utiliser des expressions lambda dans du code qui devraient rester rétrocompatibles et fonctionner dans Java 7. Vous n'avez pas de chance. Le compilateur Java ne vous permettra pas de spécifier une version du code source au-dessus de la cible. Donc, si vous définissez le niveau de conformité du code source sur 1,8 (c'est-à-dire Java 8) et que le niveau de conformité cible est 1,7 (Java 7), le compilateur ne vous le permettra pas.
Les fichiers JAR multi-versions vous aiderontPlus récemment, une autre grande opportunité est apparue d'utiliser les dernières fonctionnalités Java, tout en permettant aux applications de fonctionner avec les anciennes versions de Java, où ces applications n'étaient pas prises en charge. Dans Java 9, cette fonctionnalité est fournie à la fois pour les nouvelles API et les nouvelles constructions de langage Java: nous parlons de
fichiers JAR multi-versions .
Les fichiers JAR multi-versions sont presque les mêmes que les bons vieux fichiers JAR, mais avec une mise en garde importante: une nouvelle «niche» est apparue dans les nouveaux fichiers JAR, où vous pouvez écrire des classes qui utilisent les dernières fonctionnalités de Java 9. Si vous travaillez avec Java 9, alors La JVM trouvera ce "créneau", en utilisera les classes et ignorera les classes du même nom de la partie principale du fichier JAR.
Cependant, lorsqu'elle travaille avec Java 8 ou une version antérieure, la JVM n'est pas consciente de l'existence de cette «niche». Elle l'ignore et utilise les classes de la partie principale du fichier JAR. Avec la sortie de Java 10, une nouvelle «niche» similaire apparaîtra pour les classes qui utilisent les fonctionnalités les plus pertinentes de Java 10 et ainsi de suite.
Dans
JEP 238 , une proposition de développement Java, qui décrit des fichiers JAR voraces, un exemple simple est fourni. Disons que nous avons un fichier JAR avec quatre classes fonctionnant en Java 8 ou inférieur:
JAR root - A.class - B.class - C.class - D.class
Imaginez maintenant qu'après la sortie de Java 9, nous réécrivions les classes A et B afin qu'elles puissent utiliser les nouvelles fonctionnalités spécifiques à Java 9. Ensuite, Java 10 sort, et nous réécrivons la classe A afin qu'il puisse utiliser les nouvelles fonctionnalités de Java 10. , l'application devrait toujours fonctionner correctement avec Java 8. Le nouveau fichier JAR multi-version ressemble à ceci:
JAR root - A.class - B.class - C.class - D.class - META-INF Versions - 9 - A.class - B.class - 10 - A.class
Le fichier JAR n'a pas seulement acquis une nouvelle structure; maintenant dans son manifeste, il est indiqué que ce fichier est multi-versionné.
Lorsque vous exécutez ce fichier JAR dans la machine virtuelle Java 8, il ignore la section
\META-INF\Versions
car il ne le soupçonne même pas ou ne le recherche pas. Seules les classes d'origine A, B, C et D sont utilisées.
Lors de l'exécution sous Java 9, les classes situées dans
\META-INF\Versions\9
sont utilisées, en outre, elles sont utilisées à la place des classes d'origine A et B, mais les classes dans
\META-INF\Versions\10
ignorées.
Lors de l'exécution sous Java 10, les deux branches
\META-INF\Versions
; en particulier, la version A de Java 10, la version B de Java 9 et les versions par défaut C et D.
Donc, si dans votre application vous avez besoin de la nouvelle API ProcessBuilder de Java 9, mais vous devez vous assurer que l'application continue de fonctionner sous Java 8, écrivez simplement les nouvelles versions de vos classes à l'aide de ProcessBuilder dans la section
\META-INF\Versions\9
du fichier JAR et laissez les anciennes classes dans la partie principale de l'archive, utilisées par défaut. C'est le moyen le plus simple d'utiliser les nouvelles fonctionnalités de Java 9 sans sacrifier la compatibilité descendante.
Java 9 JDK possède une version de l'outil jar.exe qui prend en charge la création de fichiers JAR multi-versions. D'autres outils non JDK fournissent également ce support.
Java 9: modules, modules partoutLe système de modules Java 9 (également connu sous le nom de Project Jigsaw) est sans aucun doute le plus grand changement de Java 9. L'un des objectifs de la modularisation est de renforcer le mécanisme d'encapsulation de Java afin que le développeur puisse spécifier quelles API sont fournies aux autres composants et compter, que la JVM appliquera l'encapsulation. L'encapsulation est plus puissante avec la modularisation qu'avec
public/protected/private
modificateurs d'accès
public/protected/private
pour les classes ou les membres de classe.
Le deuxième objectif de la modularisation est d'indiquer quels modules ont besoin d'autres modules pour fonctionner, et avant de démarrer l'application, assurez-vous que tous les modules nécessaires sont en place. En ce sens, les modules sont plus forts que le mécanisme de chemin de classe traditionnel, car les chemins de chemin de classe ne sont pas vérifiés à l'avance et des erreurs sont possibles en raison du manque de classes nécessaires. Ainsi, un chemin de classe incorrect peut déjà être détecté lorsque l'application a le temps de fonctionner suffisamment longtemps ou après avoir été lancée plusieurs fois.
L'ensemble du système de modules est vaste et complexe, et une discussion détaillée à ce sujet dépasse le cadre de cet article (voici une bonne
explication détaillée ). Ici, je prêterai attention aux aspects de la modularisation qui aident le développeur à prendre en charge les applications héritées.
La modularisation est une bonne chose, et le développeur devrait essayer de diviser le nouveau code en modules chaque fois que possible, même si le reste de l'application est (pas encore) modularisé. Heureusement, cela est facile à faire grâce aux spécifications pour travailler avec des modules.
Tout d'abord, le fichier JAR devient modularisé (et se transforme en module) avec l'apparition du fichier module-info.class (compilé à partir de module-info.java) à la racine du fichier JAR.
module-info.java
contient des métadonnées, en particulier le nom du module dont les packages sont exportés (c'est-à-dire qui deviennent visibles de l'extérieur), les modules dont ce module a besoin et d'autres informations.
Les informations contenues dans
module-info.class
sont visibles que lorsque la machine
module-info.class
les recherche - c'est-à-dire que le système traite les fichiers JAR modulaires comme d'habitude s'il fonctionne avec des versions plus anciennes de Java (on suppose que le code a été compilé pour fonctionner avec une version plus ancienne de Java À proprement parler, cela prend un peu de chimie, et c'est toujours Java 9 qui est spécifié comme la version cible de module-info.class, mais c'est réel).
Ainsi, vous devriez pouvoir exécuter des fichiers JAR modularisés avec Java 8 et versions ultérieures, à condition qu'à d'autres égards, ils soient également compatibles avec les versions antérieures de Java. Notez également que les
module-info.class
peuvent, avec des réserves,
être placés dans des zones versionnées de fichiers JAR multi-versions .
Dans Java 9, il existe à la fois un chemin de classe et un chemin de module. et un chemin de module. Classpath fonctionne comme d'habitude. Si vous placez un fichier JAR modularisé dans un chemin de classe, il est gaspillé comme tout autre fichier JAR. Autrement dit, si vous avez modularisé le fichier JAR et que votre application n'est pas encore prête à le traiter comme un module, vous pouvez le placer dans le chemin de classe, cela fonctionnera comme toujours. Votre code hérité devrait le gérer avec succès.
Notez également que la collection de tous les fichiers JAR dans le chemin de classe est considérée comme faisant partie d'un seul module sans nom. Un tel module est considéré comme le plus courant, cependant, il exporte toutes les informations vers d'autres modules et peut faire référence à n'importe quel autre module. Ainsi, si vous ne disposez pas encore d'une application Java modularisée, mais qu'il existe certaines anciennes bibliothèques qui ne sont pas non plus modularisées (et ne le seront probablement jamais) - vous pouvez simplement placer toutes ces bibliothèques dans le chemin de classe, et l'ensemble du système fonctionnera correctement.
Java 9 a un chemin de module qui fonctionne avec le chemin de classe. Lors de l'utilisation de modules à partir de ce chemin, la JVM peut vérifier (à la fois au moment de la compilation et au moment de l'exécution) si tous les modules nécessaires sont en place et signaler une erreur si des modules manquent. Tous les fichiers JAR du chemin de classe, en tant que membres d'un module sans nom, sont accessibles aux modules répertoriés dans le chemin modulaire - et vice versa.
Il n'est pas difficile de transférer le fichier JAR du chemin de classe vers le chemin du module - et de profiter pleinement de la modularisation. Tout d'abord, vous pouvez ajouter le fichier
module-info.class
fichier JAR, puis placer le fichier JAR modularisé dans le chemin des modules. Un tel module nouvellement créé pourra toujours accéder à tous les fichiers JAR restants dans le JAR classpath, car ils entrent dans le module sans nom et restent en accès.
Il est également possible que vous ne souhaitiez pas moduler le fichier JAR ou que le fichier JAR ne vous appartienne pas, mais à quelqu'un d'autre, vous ne pouvez donc pas le moduler vous-même. Dans ce cas, le fichier JAR peut toujours être placé dans le chemin du module, il deviendra un module automatique.
Un module automatique est considéré comme un module, même s'il n'a pas de
module-info.class
. Ce module porte le même nom que le fichier JAR qu'il contient et d'autres modules peuvent le demander explicitement. Il exporte automatiquement toutes ses API accessibles au public et lit (c'est-à-dire, nécessite) tous les autres modules nommés ainsi que les modules sans nom.
Ainsi, un fichier JAR non modulaire d'un chemin de classe peut être transformé en module sans rien faire du tout. Les fichiers JAR hérités sont automatiquement convertis en modules, il leur manque juste quelques informations qui détermineraient si tous les modules nécessaires sont en place, ou déterminer ce qui manque.
Tous les fichiers JAR non modularisés ne peuvent pas être déplacés vers le chemin du module et transformés en module automatique. Il existe une règle: un
package peut faire partie d'un seul module nommé . Autrement dit, si un package se trouve dans plusieurs fichiers JAR, alors un seul fichier JAR avec ce package dans la composition peut être transformé en module automatique. Le reste peut rester dans le chemin de classe et rejoindre le module sans nom.
À première vue, le mécanisme décrit ici semble compliqué, mais en pratique il est très simple. En fait, dans ce cas, vous pouvez simplement laisser les anciens fichiers JAR dans le chemin de classe ou les déplacer vers le chemin du module. Vous pouvez les diviser en modules ou non. Et lorsque vos anciens fichiers JAR sont modularisés, vous pouvez les laisser dans le chemin de classe ou les déplacer vers le chemin du module.
Dans la plupart des cas, tout devrait simplement fonctionner, comme auparavant. Vos fichiers JAR hérités devraient prendre racine dans le nouveau système modulaire. Plus vous modulez le code, plus vous devez vérifier les informations de dépendance, et les modules et les API manquants seront détectés à des stades de développement beaucoup plus anciens et vous feront probablement économiser beaucoup de travail.
Java 9 «faites-le vous-même»: JDK et Jlink modulairesL'un des problèmes des applications Java héritées est que l'utilisateur final peut ne pas travailler avec un environnement Java approprié. Une façon de garantir l'intégrité d'une application Java consiste à fournir un runtime avec l'application. Java vous permet de créer des JRE privés (redistribuables) qui peuvent être distribués dans l'application.
Voici comment créer un JRE privé. En règle générale, la hiérarchie de fichiers JRE est prise, qui est installée avec le JDK, les fichiers nécessaires sont enregistrés et les fichiers facultatifs avec les fonctionnalités qui peuvent être nécessaires dans votre application sont enregistrés.
Le processus est un peu gênant: vous devez maintenir une hiérarchie des fichiers d'installation, tout en étant prudent, afin de ne pas manquer un seul fichier, pas un seul répertoire. Cela en soi ne fera pas de mal, cependant, vous voulez toujours vous débarrasser de tout ce qui est superflu, car ces fichiers prennent de la place. Oui, il est facile de céder et de commettre une telle erreur.
Alors pourquoi ne pas déléguer ce travail au JDK?
Dans Java 9, vous pouvez créer un environnement autonome qui est ajouté à l'application - et dans cet environnement, il y aura tout le nécessaire pour que l'application fonctionne. Vous n'avez plus à vous soucier du fait que l'ordinateur de l'utilisateur aura le mauvais environnement pour exécuter Java, vous n'avez pas à vous inquiéter du fait que vous avez vous-même mal construit un JRE privé.
Une ressource clé pour créer de telles
images exécutables autonomes est un système modulaire. Vous pouvez désormais modulariser non seulement votre propre code, mais également le JDK Java 9 lui-même. Maintenant, la bibliothèque de classes Java est une collection de modules, et les outils JDK sont également constitués de modules. Le système de modules vous oblige à spécifier les modules de classe de base nécessaires dans votre code et vous spécifiez les éléments JDK nécessaires.
Pour tout rassembler, Java 9 fournit un nouvel outil spécial appelé
jlink . En lançant jlink, vous obtenez une hiérarchie de fichiers - exactement ceux dont vous avez besoin pour exécuter votre application, ni plus, ni moins. Un tel ensemble sera beaucoup plus petit que le JRE standard, de plus, il sera spécifique à la plate-forme (c'est-à-dire qu'il sera sélectionné pour un système d'exploitation et une machine spécifiques). Par conséquent, si vous souhaitez créer de telles images exécutables pour d'autres plates-formes, vous devrez exécuter jlink dans le contexte de l'installation sur chaque plate-forme spécifique pour laquelle vous avez besoin d'une telle image.
Notez également: si vous exécutez jlink avec une application dans laquelle rien n'est modularisé, l'outil ne dispose tout simplement pas des informations nécessaires pour compresser le JRE, de sorte que jlink n'aura plus qu'à emballer l'ensemble du JRE. Même dans ce cas, cela sera un peu plus pratique pour vous: jlink emballera le JRE pour vous, vous ne pouvez donc pas vous soucier de la façon de copier correctement la hiérarchie des fichiers.
Avec jlink, il devient facile d'emballer l'application et tout ce dont vous avez besoin pour l'exécuter - et vous n'avez pas à vous inquiéter de faire quelque chose de mal. L'outil ne conditionnera que la partie du runtime nécessaire au fonctionnement de l'application. Autrement dit, une application Java héritée est garantie de recevoir un environnement dans lequel elle sera opérationnelle.
La rencontre de l'ancien et du nouveauL'un des problèmes qui se posent lors de la prise en charge d'une application Java héritée est que vous êtes privé de tous les avantages qui apparaissent lors de la sortie d'une nouvelle version. Dans Java 9, comme dans les versions précédentes, tout un tas de nouvelles API et fonctionnalités de langage sont apparues, mais les développeurs (enseignés par une expérience amère) peuvent supposer qu'ils ne pourront tout simplement pas les utiliser sans rompre la compatibilité avec les versions antérieures de Java.Nous devons rendre hommage aux concepteurs de Java 9: apparemment, ils en ont tenu compte et ont fait du bon travail pour fournir ces nouvelles fonctionnalités aux développeurs qui doivent prendre en charge les anciennes versions de Java.JAR- Java 9 JAR-, Java . , Java 9, Java 8 – .
Java, , JAR- , . , , « » .
JDK jlink, , . , Java – .
Java, Java 9 , – , , Java.