Maison  >  Article  >  Java  >  Intervieweur : Pourquoi le chargeur de classe Tomcat viole-t-il le modèle de délégation parentale ?

Intervieweur : Pourquoi le chargeur de classe Tomcat viole-t-il le modèle de délégation parentale ?

Java后端技术全栈
Java后端技术全栈avant
2023-08-17 14:36:381081parcourir

Il y a quelques jours, lors d'un entretien, un ami s'est vu poser une question sur le chargement des cours. Il a répondu régulièrement aux questions liées au chargement des cours, et il pouvait y faire face à condition de mémoriser un peu les questions de l'entretien. .

Mais si vous demandez plus profondément, juste un peu plus profondément, vous pouvez refroidir un grand nombre de personnes.

Par exemple : Pourquoi le chargeur de classe Tomcat viole-t-il le modèle de délégation parentale ?

Afin de vous éviter de rencontrer ce genre de situation lors de l'entretien, cet article arrangera cette affaire.

Nous en discuterons en 4 parties :

  1. Quel est le mécanisme de chargement des classes ?
  2. Qu'est-ce que le modèle de nomination des parents ?
  3. Comment casser le modèle de délégation parentale ?
  4. Comment le chargeur de classe Tomcat est-il conçu ?

Je pense qu'avant d'étudier le chargement des classes Tomcat, nous devrions revoir ou consolider le chargeur de classes Java par défaut. Lorsque j'ai commencé à apprendre JVM, j'étais confus quant au mécanisme de chargement de classe, j'aimerais donc profiter de cette occasion pour le partager avec vous.

1. Qu'est-ce que le mécanisme de chargement de classe ?

La conversion du résultat de la compilation du code du code machine local en bytecode est une petite étape dans le format de stockage, mais une grande étape dans le développement des langages de programmation.

La machine virtuelle Java charge les données décrivant la classe du fichier Class dans la mémoire, vérifie les données, convertit, analyse et initialise les données, et forme enfin un type Java qui peut être directement utilisé par la machine virtuelle. le mécanisme de chargement de classe de la machine virtuelle.

L'équipe de conception de la machine virtuelle a implémenté l'action "d'obtenir un flux d'octets binaires décrivant cette classe via le nom complet d'une classe" dans la phase de chargement de la classe en dehors de la machine virtuelle Java, afin que l'application puisse décider comment le faire . Obtenez la classe requise. Le module de code qui implémente cette action est appelé « chargeur de classe ».

La relation entre les classes et les chargeurs de classes

Bien que le chargeur de classe ne soit utilisé que pour implémenter l'action de chargement des classes, son rôle dans les programmes Java est loin de se limiter à l'étape de chargement des classes. Pour toute classe, le chargeur de classe qui la charge et la classe elle-même doivent établir son caractère unique dans la machine virtuelle Java. Chaque chargeur de classe possède un espace de noms de classe indépendant.

Cette phrase peut être exprimée d'une manière plus populaire : comparer si deux classes sont « égales » n'a de sens que si les deux classes sont chargées par le même chargeur de classe. Sinon, même si les deux classes proviennent du même fichier de classe. chargés par la même machine virtuelle Tant que les chargeurs de classes qui les chargent sont différents, les deux classes ne doivent pas être égales.

2. Qu'est-ce que le modèle de délégation parentale

1. Du point de vue de la machine virtuelle Java, il n'existe que deux chargeurs de classes différents : l'un est le Bootstrap ClassLoader, qui utilise le langage C++. L'implémentation (HotSpot uniquement) en fait partie. de la machine virtuelle elle-même ; l'autre est constitué de tous les autres chargeurs de classes, qui sont implémentés dans le langage Java, indépendamment de la machine virtuelle, et qui héritent tous de classes abstraitesjava.lang.ClassLoader.

2. être divisé en plus de détails. La plupart des programmeurs Java utiliseront les trois chargeurs de classes suivants fournis par le système :

  • Bootstrap ClassLoader : Ce complexe de chargeur de classe sera stocké dans le répertoire JAVA_HOME/lib ou dans le chemin spécifié par le paramètre -Xbootclasspath, et est reconnu par la machine virtuelle (uniquement selon la reconnaissance du nom du fichier, tel que rt.jar, les bibliothèques de classes avec des noms incohérents ne seront pas surchargées même si elles sont placées dans le répertoire lib).
  • Extension ClassLoader : Ce chargeur de classe se compose de sun.misc.Launcher$ExtClassLoader, qui est responsable du mélange de toutes les bibliothèques de classes dans le répertoire JAVA_HOME/lib/ext ou dans le chemin spécifié par la variable système java.ext.dirs. Les développeurs peuvent utiliser directement le chargeur de classe d'extension. sun.misc.Launcher$ExtClassLoader实现,它负责夹杂JAVA_HOME/lib/ext 目录下的,或者被java.ext.dirs 系统变量所指定的路径中的所有类库。开发者可以直接使用扩展类加载器。
  • 应用程序类加载器(Application ClassLoader): 这个类加载器由sun.misc.Launcher$AppClassLoader

Application ClassLoader :

Ce chargeur de classe se compose de implémentation de sun.misc.Launcher$AppClassLoader. Étant donné que ce chargeur de classe est la valeur de retour de la méthode getSystemClassLoader dans ClassLoader, il devient également le chargeur de classe système. Il est responsable du chargement de la bibliothèque de classes spécifiée sur le chemin de classe utilisateur (ClassPath). Les développeurs peuvent utiliser ce chargeur de classe directement. Si l'application ne définit pas son propre chargeur de classe, il s'agit généralement du chargeur de classe par défaut dans le programme.
Intervieweur : Pourquoi le chargeur de classe Tomcat viole-t-il le modèle de délégation parentale ?
La relation entre ces chargeurs de classe est généralement celle indiquée dans la figure ci-dessous :

image

La relation entre chaque chargeur de classe dans la figure est appelée

Modèle de délégation parentale du chargeur de classe
(Mode de délégation des parents ). Le modèle de délégation parent nécessite qu'en plus du chargeur de classe de démarrage de niveau supérieur, tous les autres chargeurs de classe soient chargés par leurs propres chargeurs de classe parent. La relation parent-enfant entre les chargeurs de classe ici n'est généralement pas réalisée par héritage, mais par héritage. . Ils utilisent tous des relations de composition pour réutiliser le code du chargeur parent.

🎜Remarque : Au cours de l'entretien, l'intervieweur peut également demander quel répertoire charge le chargeur de classe correspondant. 🎜🎜🎜Le modèle de délégation parent des chargeurs de classes a été introduit lors du JDK 1.2 et est largement utilisé dans tous les programmes Java ultérieurs. Cependant, il ne s'agit pas d'un modèle de contrainte obligatoire, mais il est recommandé aux développeurs par les concepteurs Java. 🎜

Le processus de fonctionnement du modèle de délégation parent est le suivant : si un chargeur de classe reçoit une demande de chargement de classe, il n'essaiera pas de charger la classe lui-même en premier, mais délègue la demande au chargeur de classe parent pour qu'elle la termine. Cela est vrai pour chaque niveau de chargeur de classe, donc toutes les demandes de chargement doivent finalement être envoyées au chargeur de classe de démarrage de niveau supérieur uniquement lorsque le chargeur parent indique qu'il ne peut pas terminer la demande (la demande requise n'est pas trouvée dans sa portée de recherche. ) classe), le sous-chargeur essaiera de le charger tout seul.

Pourquoi fais-tu ça ?

Si le modèle de délégation parent n'est pas utilisé et que chaque chargeur de classe le charge lui-même, si l'utilisateur écrit une classe appelée java.lang.Object et la place dans le ClassPath du programme, le système aura plusieurs différences. , le comportement le plus élémentaire du système de types Java ne peut pas être garanti. Les candidatures deviendront également un gâchis.

Comment le modèle de délégation parentale est-il mis en œuvre ?

est très simple : tout le code est dans la méthode loadClass dans java.lang.ClassLoader, le code est le suivant :

Intervieweur : Pourquoi le chargeur de classe Tomcat viole-t-il le modèle de délégation parentale ?
picture

La logique est claire et facile à comprendre : vérifiez d'abord s'il a été chargé, si non, appelez la méthode LoadClass du chargeur parent du chargeur. Si le chargeur parent est vide, le chargeur de classe de démarrage sera utilisé comme chargeur parent par défaut. Si le chargement de la classe parent échoue, lancez une exception ClassNotFoundException, puis appelez sa propre méthode findClass pour la charger.

3. Comment casser le modèle de délégation parentale ?

Nous venons de dire que le modèle de délégation parent n'est pas un modèle de contrainte obligatoire, mais une implémentation suggérée d'un chargeur de classe. La plupart des chargeurs de classes dans le monde Java suivent ce modèle, mais il existe des exceptions. Jusqu'à présent, le modèle de délégation parentale a été "cassé" à grande échelle à trois reprises.

La première fois : avant l'émergence du modèle de délégation parentale-----avant la sortie du JDK1.2.

Deuxième fois : Cela est causé par les défauts de ce modèle lui-même. Nous disons que le modèle de délégation parent résout très bien le problème de l'unification des classes de base de chaque chargeur de classe (plus les classes de base sont chargées par les chargeurs de niveau supérieur). La raison pour laquelle les classes de base sont appelées "de base" est parce que. C'est toujours une API appelée par le code utilisateur, mais il n'y a aucune garantie. Et si la classe de base appelle le code utilisateur ?

Ce n'est pas impossible. Un exemple typique est le service JNDI qui est désormais un service standard en Java. Son code est chargé par le chargeur de classe de démarrage (rt.jar qui a été placé dans JDK1.3), mais il doit être appelé et implémenté par un indépendant. et déployez le code du fournisseur d'interface JNDI (SPI, Service Provider Interface) sous le ClassPath de l'application, mais il est impossible pour le chargeur de classe de démarrage de "connaître" ces codes. Parce que ces classes ne sont pas en rt.jar , Mais le chargeur de classe de démarrage doit être chargé à nouveau. Ce qu'il faut faire? rt.jar中,但是启动类加载器又需要加载。怎么办呢?

为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread

Afin de résoudre ce problème, l'équipe de conception Java a dû introduire un design moins élégant : Thread Context ClassLoader. Ce chargeur de classe peut être transmis ,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">java .lang.Thread La méthode setContextClassLoader de la classe code> est définie. S'il n'a pas été défini lors de la création du thread, il héritera de celui du thread parent. S'il n'est pas trop défini dans la portée globale de l'application, ce chargeur de classe sera par défaut le chargeur de classe d'application.

Hé, avec le chargeur de contexte de thread, le service JNDI utilise ce chargeur de contexte de thread pour charger le code SPI requis, c'est-à-dire que le chargeur de classe parent demande au chargeur de classe enfant de terminer l'action de chargement de classe. la structure hiérarchique du modèle de délégation parentale pour utiliser à l'envers le chargeur de classe, ce qui viole en fait les principes généraux du modèle de délégation parentale. Mais vous ne pouvez rien y faire. Toutes les actions de chargement impliquant SPI en Java utilisent essentiellement cette méthode. Par exemple JNDI, JDBC, JCE, JAXB, JBI, etc.

La troisième fois : afin de réaliser un branchement à chaud, un déploiement à chaud et une modularisation, ce qui signifie ajouter une fonction ou soustraire une fonction sans redémarrer, il vous suffit de remplacer le module avec le chargeur de classe pour réaliser le remplacement de code à chaud.

Le livre dit également :

Il existe fondamentalement un consensus dans les programmes Java : l'utilisation des chargeurs de classes par OSGI mérite d'être apprise. Comprendre l'implémentation d'OSGI peut être considéré comme maîtriser l'essence des chargeurs de classes.

Génial ! ! !

Maintenant, nous avons essentiellement compris le principe de fonctionnement du chargement des classes par défaut Java, et nous connaissons également le modèle de délégation parent. Cela dit, j'ai presque oublié notre Tomcat. Notre sujet est pourquoi le chargeur Tomcat viole-t-il le modèle de délégation parentale ?

Parlons de notre chargeur de classe Tomcat.

4. Comment le chargeur de classes de Tomcat est-il conçu ?

Tout d'abord, posons une question :

Est-il acceptable si Tomcat utilise le mécanisme de chargement de classe par défaut ?

🎜

Réfléchissons : Tomcat est un conteneur Web, alors quels problèmes doit-il résoudre :

  • Un conteneur Web peut devoir déployer deux applications, et différentes applications peuvent s'appuyer sur différentes versions de la même classe tierce. bibliothèque. Ce n’est pas possible. Il est nécessaire qu’il n’y ait qu’une seule copie de la même bibliothèque de classes sur le même serveur, il faut donc s’assurer que les bibliothèques de classes de chaque application sont indépendantes et isolées les unes des autres.
  • La même bibliothèque de classes et la même version déployée dans le même conteneur Web peuvent être partagées. Sinon, si le serveur dispose de 10 applications, alors 10 copies de la même bibliothèque de classes doivent être chargées dans la machine virtuelle, ce qui n'a aucun sens.
  • Le conteneur Web possède également ses propres bibliothèques de classes dépendantes, qui ne peuvent pas être confondues avec les bibliothèques de classes de l'application. Pour des raisons de sécurité, la bibliothèque de classes du conteneur doit être isolée de la bibliothèque de classes du programme.
  • Le conteneur Web doit prendre en charge la modification de jsp. Nous savons que le fichier jsp doit éventuellement être compilé dans un fichier de classe avant de pouvoir être exécuté dans une machine virtuelle. Cependant, il est courant de modifier jsp après l'exécution du programme. courir. Sinon, à quoi vous serviriez-vous ? Par conséquent, le conteneur Web doit prendre en charge la modification jsp sans redémarrer.

Regardons à nouveau notre question : est-il acceptable que Tomcat utilise le mécanisme de chargement de classe par défaut ?

La réponse est non. Pourquoi? Examinons le premier problème. Si vous utilisez le mécanisme de chargement de classes par défaut, vous ne pouvez pas charger deux versions différentes de la même bibliothèque de classes. L'accumulateur par défaut ne se soucie que de votre classe complète, quel que soit le nom de votre version. copie.

La deuxième question est que le chargeur de classe par défaut peut être implémenté car sa responsabilité est de garantir l'unicité. La troisième question est la même que la première question. Regardons à nouveau la quatrième question. Nous réfléchissons à la manière d'implémenter la modification à chaud des fichiers jsp (le nom donné par l'affiche) est en fait un fichier de classe, donc s'il est modifié, le nom de la classe est toujours le même. de même, le chargeur de classe le fera directement. Si le jsp existe déjà dans la zone méthode est récupéré, le jsp modifié ne sera pas rechargé.

Alors que devons-nous faire ? On peut désinstaller directement le chargeur de classe de ce fichier jsp, vous auriez donc dû penser que chaque fichier jsp correspond à un chargeur de classe unique. Lorsqu'un fichier jsp est modifié, le chargeur de classe jsp est directement déchargé. Recréez le chargeur de classe et rechargez le fichier jsp.

Comment Tomcat implémente-t-il son propre mécanisme de chargement de classe ?

Alors, comment Tomcat l'implémente-t-il ? La formidable équipe Tomcat l'a déjà conçu. Jetons un coup d'œil à leurs dessins de conception :

Intervieweur : Pourquoi le chargeur de classe Tomcat viole-t-il le modèle de délégation parentale ?
Picture

Nous voyons que les trois premiers chargements de classe sont cohérents avec ceux par défaut. CommonClassLoader, CatalinaClassLoader, SharedClassLoader et WebappClassLoader sont les propres chargeurs de classe de Tomcat. Ils chargent respectivement/common/*/server/*/shared/*(在tomcat 6之后已经合并到根目录下的lib目录下)和/WebApp/WEB-INF/*中的Java类库。其中WebApp类加载器和Jsp类加载器通常会存在多个实例,每一个Web应用程序对应一个WebApp类加载器,每一个JSP文件对应一个Jsp类加载器。

  • commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
  • catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
  • sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
  • WebappClassLoader : des chargeurs de classes privés pour chaque Webapp, les classes dans. le chemin de chargement n'est visible que par la Webapp actuelle ;

Cela peut être vu à partir de la relation de délégation dans la figure :

Les classes qui peuvent être chargées par CommonClassLoader peuvent être utilisées par Catalina ClassLoader et SharedClassLoader, réalisant ainsi l'utilisation de bibliothèques de classes publiques Shared, tandis que les classes que CatalinaClassLoader et Shared ClassLoader peuvent charger sont isolées les unes des autres.

WebAppClassLoader peut utiliser les classes chargées par SharedClassLoader, mais les instances individuelles de WebAppClassLoader sont isolées les unes des autres.

La portée de chargement de JasperLoader est uniquement le fichier .Class compilé par ce fichier JSP. Le but de son apparition est d'être ignoré : lorsque le conteneur Web détecte que le fichier JSP a été modifié, il remplacera l'instance JasperLoader actuelle. , et implémentez la fonction HotSwap des fichiers JSP en créant un nouveau chargeur de classe Jsp.

D'accord, jusqu'à présent, nous savons déjà pourquoi Tomcat est conçu de cette façon et comment il est conçu. Alors, Tomcat viole-t-il le modèle de délégation parentale recommandé par Java ? La réponse est : violé.

Nous l'avons déjà dit :

Le modèle de délégation parent exige qu'à l'exception du chargeur de classe de démarrage de niveau supérieur, tous les autres chargeurs de classe soient chargés par leur propre chargeur de classe parent.

Évidemment, Tomcat n'est pas implémenté de cette façon. Afin d'obtenir l'isolement, Tomcat ne respecte pas cette convention. Chaque webappClassLoader charge le fichier de classe dans son propre répertoire et ne le transmettra pas au chargeur de classe parent.

Nous posons une question : que devons-nous faire si le Common ClassLoader de Tomcat souhaite charger des classes dans WebApp ClassLoader ? Après avoir lu le contenu précédent sur la destruction du modèle de délégation parent, nous avons une idée claire. Nous pouvons utiliser le chargeur de classe de contexte de thread pour l'implémenter. En utilisant le chargeur de contexte de thread, le chargeur de classe parent peut demander au chargeur de classe enfant de terminer la classe. action de chargement.

N'est-ce pas génial ?

Résumé

D'accord, enfin, nous comprenons pourquoi Tomcat viole le modèle de délégation parent, et nous savons également comment le chargeur de classe de Tomcat est conçu. À propos, j'ai passé en revue le mécanisme de chargement de classe par défaut de Java et j'ai également appris comment détruire le mécanisme de chargement de classe de Java. Cette fois, la récolte n'est pas petite ! ! !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer