Implementierungsmethode und Beispielcode des Tomcat-Klassenladers

Implementierungsmethode und Beispielcode des Tomcat-Klassenladers

Tomcat definiert intern mehrere ClassLoader, sodass Anwendungen und Container auf Klassen und Ressourcen in verschiedenen Repositories zugreifen können und gleichzeitig eine Klassenisolation zwischen den Anwendungen erreicht wird.

1. Java-Klassenlademechanismus

Beim Laden von Klassen werden die kompilierten Klassendateien in den JVM-Speicher geladen (permanente Generierung/Metaspace).

Der Grund, warum Klassenlader eine Klassenisolation erreichen können, besteht darin, dass zwei Klassen nur dann gleich sind, wenn sie vom selben Klassenlader geladen werden, andernfalls müssen sie ungleich sein.

Beim Laden verwendet die JVM einen übergeordneten Delegationsmechanismus. Wenn der Klassenlader eine Klasse laden möchte, lautet die Ladereihenfolge:

Zunächst wird die Anforderung an den übergeordneten Loader delegiert. Wenn der übergeordnete Loader die zu ladende Klasse nicht finden kann, sucht er in seinem eigenen Repository nach dem Laden.

Der Vorteil dieses Mechanismus besteht darin, dass sichergestellt werden kann, dass die Kernklassenbibliothek nicht überschrieben wird.

Gemäß der Servlet-Spezifikation ist der Webapp-Loader etwas anders. Er sucht zuerst in seiner eigenen Ressourcenbibliothek, anstatt nach oben zu delegieren, wodurch der Standarddelegierungsmechanismus unterbrochen wird. Werfen wir einen Blick auf das Design und die Implementierung von Tomcat.

2. Entwurf des Tomcat-Klassenladers

Die allgemeine Klassenladerstruktur von Tomcat ist wie folgt:

Die von JDK bereitgestellten Klassenlader sind:

Bootstrap – Bootstrap-Klassenlader, Teil der JVM, lädt bestimmte Dateien in das Verzeichnis <JAVA_HOME>/lib/. Erweiterung – Erweiterungsklassenlader, lädt Klassenbibliotheken in das Verzeichnis <JAVA_HOME>/lib/ext/. Anwendung – Anwendungsklassenlader, auch Systemklassenlader genannt, lädt durch CLASSPATH angegebene Klassenbibliotheken.

Die von Tomcat implementierten Klassenlader sind:

Common – Der übergeordnete Loader ist AppClassLoader, der standardmäßig die Klassenbibliotheken im Verzeichnis ${catalina.home}/lib/ lädt. Catalina – Der übergeordnete Loader ist der Common-Klassenloader, der die von server.loader in der Konfigurationsdatei catalina.properties konfigurierten Ressourcen lädt. Dies sind im Allgemeinen die von Tomcat intern verwendeten Ressourcen. Shared – Der übergeordnete Loader ist der Common-Klassenloader, der die von shared.loader in der Konfigurationsdatei catalina.properties konfigurierten Ressourcen lädt. Dies sind im Allgemeinen die von allen Web-Anwendungen gemeinsam genutzten Ressourcen. WebappX – Der übergeordnete Loader ist der Shared Loader, der die Klassen in /WEB-INF/classes und die JAR-Pakete in /WEB-INF/lib/ lädt. JasperLoader – Der übergeordnete Loader ist der Webapp-Loader, der die durch das Kompilieren von JSP-Anwendungen generierten Klassendateien im Arbeitsverzeichnis lädt.

In der Implementierung handelt es sich beim obigen Diagramm nicht um eine Vererbungsbeziehung, sondern um eine Eltern-Kind-Beziehung durch Kombination. Quellcode-Klassendiagramm des Tomcat-Klassenladers:

Common, Catalina und Shared sind alle Instanzen von StandardClassLoader. Standardmäßig beziehen sie sich auf dasselbe Objekt. Es gibt keinen Unterschied zwischen StandardClassLoader und URLClassLoader. WebappClassLoader sucht und lädt gemäß der Spezifikation in der folgenden Reihenfolge:

Geladen aus dem Bootstrap-Repository innerhalb der JVM. Geladen aus dem Anwendungsladerpfad, d. h. CLASSPATH. Geladen aus dem Verzeichnis /WEB-INF/classes im Webprogramm. Aus der JAR-Datei in /WEB-INF/lib im Webprogramm. Geladen aus dem Container. Gemeinsames Loader-Repository, d. h. die von allen Webprogrammen gemeinsam genutzten Ressourcen.

Schauen wir uns als Nächstes die Implementierung des Quellcodes an.

3. Initialisierung des benutzerdefinierten Loaders

Der allgemeine Klassenlader wird in initClassLoaders von Bootstrap initialisiert. Der Quellcode lautet wie folgt:

private void initClassLoaders() {
 versuchen {
 commonLoader = createClassLoader("common", null);
 wenn( commonLoader == null ) {
  // keine Konfigurationsdatei, standardmäßig dieser Loader – wir befinden uns möglicherweise in einer „einzelnen“ Umgebung.
  commonLoader=dies.getClass().getClassLoader();
 }
 //Geben Sie das Präfix der Konfigurationsdatei des Repository-Pfads und den übergeordneten Loader an und erstellen Sie eine ClassLoader-Instanz catalinaLoader = createClassLoader("server", commonLoader);
 sharedLoader = createClassLoader("geteilt", commonLoader);
 } fangen (Wurfbares t) {
 log.error("Beim Erstellen des Klassenladers kam es zu einer Ausnahme", t);
 System.exit(1);
 }
}

Sie können sehen, dass drei Klassenlader erstellt werden. CreateClassLoader erhält die Ressourcenlageradresse gemäß der Konfiguration und gibt schließlich eine StandardClassLoader-Instanz zurück. Der Kerncode lautet wie folgt:

privater ClassLoader createClassLoader (String-Name, übergeordneter ClassLoader)
 wirft Ausnahme {

 Zeichenfolgenwert = CatalinaProperties.getProperty(name + ".loader");
 wenn ((Wert == null) || (Wert.gleich("")))
  return parent; // Wenn nicht konfiguriert, gib den übergebenen übergeordneten Loader zurück. ArrayList repositoryLocations = new ArrayList();
 ArrayList repositoryTypes = neue ArrayList();
 ...
 // Den Pfad zum Ressourcen-Repository abrufen String[] locations = (String[]) repositoryLocations.toArray(new String[0]);
 Integer[] Typen = (Integer[]) repositoryTypes.toArray(neue Integer[0]);
 //Erstellen Sie ein StandardClassLoader-Objekt ClassLoader classLoader = ClassLoaderFactory.createClassLoader
   (Standorte, Typen, übergeordnetes Element);
 ...
 gib classLoader zurück;
}

Nachdem der Klassenlader initialisiert wurde, wird ein Catalina-Objekt erstellt und seine Lademethode wird schließlich aufgerufen, um server.xml zu analysieren und die internen Komponenten des Containers zu initialisieren. In welcher Beziehung steht also ein Container wie Engine zum übergeordneten Loader dieser Einstellung?

Das Catalina-Objekt hat eine Mitgliedsvariable parentClassLoader, die der übergeordnete Loader aller Komponenten ist. Der Standardwert ist AppClassLoader. Wenn dieses Objekt erstellt wird, wird seine Methode setParentClassLoader berücksichtigt und der übergeordnete Loader wird auf sharedLoader gesetzt.

Wenn die Container-Engine der obersten Ebene in Tomcat initialisiert wird, verfügt Digester über eine SetParentClassLoaderRule-Regel, die den parentClassLoader von Catalina über die Methode Engine.setParentClassLoader verknüpft.

4. So unterbrechen Sie den übergeordneten Delegationsmechanismus

Die Antwort besteht darin, Thread.getContextClassLoader() zu verwenden – den Kontextlader für den aktuellen Thread, der dynamisch festgelegt werden kann, während Ihr Code über Thread.setContextClassLoader() ausgeführt wird.

Standardmäßig wird der Thread-Kontextlader vom übergeordneten Thread geerbt. Dies bedeutet, dass der Standardkontextlader aller Threads derselbe ist wie der des zuerst gestarteten Threads, d. h. des Hauptthreads, und sein Kontextlader ist AppClassLoader.

Beim Start von StandardContext initialisiert Tomcat zunächst einen WebappClassLoader und setzt ihn dann als Kontextlader des aktuellen Threads. Abschließend kapselt er ihn als Loader-Objekt ein und verwendet ihn beim Laden von Servlet-Klassen mithilfe der Eltern-Kind-Beziehung zwischen Containern.

5. Klassenladen für Webanwendungen

Das Laden der Klassen der Webanwendung wird durch die WebappClassLoader-Methode loadClass(String, boolean) abgeschlossen. Der Kerncode lautet wie folgt:

Überschreiben von J2SE verhindern

öffentliche synchronisierte Klasse loadClass (String-Name, Boolesche Auflösung)
 wirft ClassNotFoundException {
 ...
 Klasse clazz = null;
 // (0) Prüfen, ob es in den internen Cache geladen wurde clazz = findLoadedClass0(name);
 if (clazz != null) {
 wenn (log.isDebugEnabled())
  log.debug("Klasse aus Cache zurückgeben");
 wenn (auflösen) AuflöseKlasse(clazz);
 Rückkehr (Clazz);
 }
 // (0.1) Prüfen, ob es bereits in den JVM-Cache geladen ist clazz = findLoadedClass(name);
 if (clazz != null) {
 wenn (log.isDebugEnabled())
  log.debug("Klasse aus Cache zurückgeben");
 wenn (auflösen) AuflöseKlasse(clazz);
 Rückkehr (Clazz);
 }
 // (0.2) Versuchen Sie, den Systemklassenlader zu verwenden, um das Überschreiben der J2SE-Klasse zu verhindern. Versuchen Sie {
 clazz = system.loadClass(name);
 if (clazz != null) {
  wenn (auflösen) AuflöseKlasse(clazz);
  Rückkehr (Clazz);
 }
 } catch (ClassNotFoundException e) { // Ignorieren }
 // (0.5) Verwenden Sie SecurityManager, um zu überprüfen, ob Sie Zugriff auf diese Klasse haben, if (securityManager != null) {
 int i = name.letzterIndexVon('.');
 wenn (i >= 0) {
  versuchen {
  securityManager.checkPackageAccess(name.substring(0,i));
  } Fang (Sicherheitsausnahme se) {
  String-Fehler = "Sicherheitsverletzung, Versuch der Verwendung von " +
   "Eingeschränkte Klasse: " + Name;
  log.info(Fehler, se);
  wirf eine neue ClassNotFoundException (Fehler, se);
  }
 }
 }
 Boolescher Wert delegateLoad = Delegierter || Filter(Name);
 // (1) Ob an die übergeordnete Klasse delegiert werden soll, der Standardwert ist hier false
 wenn (delegateLoad) {
  ...
 }
 // (2) Versuche, dein eigenes Repository zu finden und zu laden try {
 clazz = Klasse finden(Name);
 if (clazz != null) {
  wenn (log.isDebugEnabled())
  log.debug("Klasse aus lokalem Repository laden");
  wenn (auflösen) AuflöseKlasse(clazz);
  Rückkehr (Clazz);
 }
 } Fang (ClassNotFoundException e) {}
 // (3) Wenn das Laden an dieser Stelle immer noch fehlschlägt, delegiere die Ladeanforderung an den übergeordneten Loader if (!delegateLoad) {
 wenn (log.isDebugEnabled())
  log.debug("Delegieren an übergeordneten Klassenlader am Ende: " + übergeordneter Klassenlader);
 ClassLoader-Lader = übergeordnetes Element;
 wenn (Loader == null)
  Lader = System;
 versuchen {
  clazz = loader.loadClass(name);
  if (clazz != null) {
  wenn (log.isDebugEnabled())
   log.debug("Klasse vom übergeordneten Element wird geladen");
  wenn (auflösen) AuflöseKlasse(clazz);
  Rückkehr (Clazz);
  }
 } Fang (ClassNotFoundException e) {}
 }
 //Schließlich schlägt das Laden fehl und eine Ausnahme wird ausgelöst. throw new ClassNotFoundException(name);
}

Um das Überschreiben von J2SE-Klassen zu verhindern, verwendet Tomcat 6 den AppClassLoader. Die rt.jar-Kernklassenbibliothek wird vom Bootstrap Classloader geladen, dieser Loader ist jedoch im Java-Code nicht verfügbar. In höheren Versionen wurden die folgenden Optimierungen vorgenommen:
ClassLoader j = String.class.getClassLoader();
wenn (j == null) {
 j = getSystemClassLoader();
 während (j.getParent() != null) {
 j = j.getParent();
 }
}
dies.javaseClassLoader = j;

Beim Laden von Klassen verwendet Tomcat 6 den AppClassLoader. Die rt.jar-Kernklassenbibliothek wird vom Bootstrap Classloader geladen, dieser Loader kann jedoch nicht im Java-Code abgerufen werden. In höheren Versionen wurden die folgenden Optimierungen vorgenommen:

ClassLoader j = String.class.getClassLoader();
wenn (j == null) {
 j = getSystemClassLoader();
 während (j.getParent() != null) {
 j = j.getParent();
 }
}
dies.javaseClassLoader = j;

Das heißt, verwenden Sie einen Klassenlader, der dem Bootstrap-Lader möglichst nahe kommt.

6. Zusammenfassung

Ich glaube, die meisten Leute sind schon einmal auf die Ausnahme ClassNotFoundException gestoßen. Dahinter steckt der Klassenlader. Ein gewisses Verständnis des Ladeprinzips hilft bei der Behebung des Problems.

Oben ist die vom Herausgeber eingeführte Implementierungsmethode und der Beispielcode des Tomcat-Klassenladers. Ich hoffe, es wird allen helfen. Wenn Sie Fragen haben, hinterlassen Sie mir bitte eine Nachricht und der Herausgeber wird Ihnen rechtzeitig antworten. Ich möchte auch allen für ihre Unterstützung der Website 123WORDPRESS.COM danken!
Wenn Sie diesen Artikel hilfreich finden, können Sie ihn gerne abdrucken und dabei bitte die Quelle angeben. Vielen Dank!

Das könnte Sie auch interessieren:
  • Lösen Sie das Problem, dass Eclipse beim Starten von Tomcat keine Webprojekte laden kann
  • Analyse und Lösung des abnormalen Problems beim Laden von JAR in Tomcat
  • Detaillierte Erläuterung der Hot-Deployment- und Hot-Loading-Methoden von Tomcat
  • Lösung für Tomcats Fehler beim Laden statischer Ressourcendateien wie CSS und JS
  • Kennen Sie den Klassenlader und Sicherheitsmechanismus in Java Tomcat?

<<:  Zehn nützliche und einfache MySQL-Funktionen

>>:  So fügen Sie in JS eine Abbruchfunktion zu einem Versprechen hinzu

Artikel empfehlen

Probleme und Vorsichtsmaßnahmen beim Festlegen von maxPostSize für Tomcat

1. Warum maxPostSize festlegen? Der Tomcat-Contai...

Unterschiede zwischen FLOW CHART und UI FLOW

Viele Konzepte im UI-Design mögen in der Formulie...

So installieren Sie Babel mit NPM in VSCode

Vorwort Im vorherigen Artikel wurde die Installat...

Praktische Methode zum Löschen einer Zeile in einer MySql-Tabelle

Zunächst müssen Sie bestimmen, welche Felder oder...

Schritte zur Lösung des Zeitzonenproblems in MySQL 8.0

Softwareversion Windows: Windows 10 MySQL: mysql-...

So finden Sie den angegebenen Inhalt einer großen Datei in Linux

Denken Sie im Großen und im Kleinen und lenken Si...

Detaillierte Erklärung zur Installation von MariaDB 10.2.4 auf CentOS7

CentOS 6 und frühere Versionen stellen MySQL-Serv...

Detaillierte Schritte zur Installation und Konfiguration von MySQL 5.7

1. MySQL herunterladen 1. Melden Sie sich auf der...

Detaillierte Erläuterung der Nginx-Zugriffsbeschränkungskonfiguration

Was ist die Nginx-Zugriffsbeschränkungskonfigurat...

Spezifische Verwendung des Linux-gcc-Befehls

01. Befehlsübersicht Der Befehl gcc verwendet den...

Detaillierte Erläuterung der Angular-Routing-Unterrouten

Inhaltsverzeichnis 1. Subroutensyntax 2. Beispiel...