Detaillierte Erklärung, wie Tomcat die Servlet-Initialisierung aus der Quellcodeanalyse aufruft

Detaillierte Erklärung, wie Tomcat die Servlet-Initialisierung aus der Quellcodeanalyse aufruft

Einführung

Im vorherigen Blog haben wir den Tomcat-Quellcode erfolgreich lokal ausgeführt. In diesem Blog werden wir daher auf Quellcodeebene analysieren, wie Tomcat den Servlet-Container beim Start initialisiert. Normalerweise stellen wir unsere Dienste auf Tomcat bereit, ändern dann die Konfigurationsdatei und starten sie, um Dienste für die Außenwelt bereitzustellen. Über einige der Prozesse wissen wir jedoch nicht viel, beispielsweise wie web.xml geladen wird. Dies ist für uns ein wesentlicher Prozess, um Servlet und sringMVC zu analysieren.

Adresse der Annotationsquelle: https://github.com/good-jack/tomcat_source/tree/master

1. Code zum Starten von Tomcat

Normalerweise starten wir Tomcat, egal ob unter Windows oder Linux, über Skripte. Das ist für uns nicht sehr benutzerfreundlich, wenn wir den Quellcode analysieren möchten. Daher müssen wir ihn über Code starten. Der Startcode lautet wie folgt:

Tomcat Tomcat = neuer Tomcat();
        tomcat.setPort(8080);
        //Jede Containerebene neu erstellen und die Beziehung zwischen den Containerebenen aufrechterhalten tomcat.addWebapp("/","/");
        tomcat.start();
        //Blockieren Sie den Abhörport tomcat.getServer().await();

Der Startcode ist immer noch sehr einfach. Aus dem Code können wir erkennen, dass dieser Blog hauptsächlich die Methoden addWebapp () und start () analysiert. Durch diese beiden Methoden können wir herausfinden, wann der Servlet-Container initialisiert wird.

2. Tomcat-Framework

Bevor wir die beiden oben genannten Methoden analysieren, fassen wir das grundlegende Framework von Tomcat zusammen. Tatsächlich können wir aus der uns sehr vertrauten Konfigurationsdatei server.xml erkennen, dass Tomcat aus einer Reihe von übergeordneten und untergeordneten Containern besteht:

Server ---> Service --> Connector Engine addChild---> Kontext (Servlet-Container). Dies sind die Container, die wir aus der Konfigurationsdatei analysiert haben. Beim Start von Tomcat werden die Container Schicht für Schicht gestartet.

3. Erstellen Sie einen Container (addWebapp())

3.1 Flussdiagramm für Methodenaufrufe

Das obige Flussdiagramm zeigt mehrere wichtige Methoden, die schrittweise aus dem Quellcode analysiert werden, was für uns bei der Analyse des Quellcodes sehr hilfreich ist.

3.2 Quellcode-Analyse

1) Holen Sie sich den configContext-Listener durch Reflektion

Methodenpfad: Paket org.apache.catalina.startup.Tomcat.addWebapp(Host host, String contextPath, String docBase);

 
    öffentlicher Kontext addWebapp(Host host, String contextPath, String docBase) {
        //Holen Sie sich einen Listener ContextConfig durch Reflektion,
        //Die durch Reflexion erhaltene Klasse muss eine Implementierungsklasse von LifecycleListener sein. Geben Sie getConfigClass ein, um die Implementierungsklasse abzurufen (org.apache.catalina.startup.ContextConfig).
        LifecycleListener-Listener = null;
        versuchen {
            Klasse<?> clazz = Klasse.forName(getHost().getConfigClass());
            listener = (LifecycleListener) clazz.getConstructor().newInstance();
        } Fang (ReflectiveOperationException e) {
            // In IAE einbinden, da wir die Methodensignatur nicht einfach ändern können in
            // um die spezifischen geprüften Ausnahmen auszulösen
            wirf eine neue IllegalArgumentException(e);
        }
 
        gibt addWebapp zurück (Host, Kontextpfad, DocBase, Listener);
    }

2) Holen Sie sich einen Kontextcontainer (StandardContext)

Im folgenden Code lädt die Methode createContext() den StandardContext-Container durch Reflektion und legt den Listener ContextConfig fest, ctx.addLifecycleListener(config);

öffentlicher Kontext addWebapp(Host host, String contextPath, String docBase,
            LifecycleListener-Konfiguration) {
 
        Stille (Host, Kontextpfad);
 
        //Holen Sie sich einen Kontextcontainer (StandardContext)
        Kontext ctx = Kontext erstellen(Host, Kontextpfad);
        ctx.setPath(Kontextpfad);
        ctx.setDocBase(docBase);
 
        wenn (addDefaultWebXmlToWebapp) {
            ctx.addLifecycleListener(getDefaultWebXmlListener());
        }
 
        ctx.setConfigFile(getWebappConfigFile(docBase, Kontextpfad));
        //Fügen Sie den Listener zum Kontext hinzu ctx.addLifecycleListener(config);
 
        if (addDefaultWebXmlToWebapp && (config-Instanz von ContextConfig)) {
            // Verhindern Sie, dass es sucht (wenn es eines findet, tritt ein Dup-Fehler auf)
            ((ContextConfig) config).setDefaultWebXml(noDefaultWebXmlPath());
        }
 
        wenn (Host == null) {
            //getHost erstellt Container Schicht für Schicht und verwaltet die Eltern-Kind-Beziehung der Container getHost().addChild(ctx);
        } anders {
            host.addChild(ctx);
        }
 
        ctx zurückgeben;
    }

3) Wartung von Containern auf allen Ebenen

Die Methode getHost() ruft Container auf jeder Ebene ab und verwaltet die übergeordnete Containerbeziehung, einschließlich des Server-Containers und des Engine-Containers. Und der StandardContext-Container wird in der untergeordneten Karte verwaltet, indem die Methode addChild() in containerBase über getHost().addChild(ctx); aufgerufen wird.

  öffentlicher Host getHost() {
        //Für jede LayerEngine neue Container erstellen engine = getEngine();
        wenn (engine.findChildren().Länge > 0) {
            return (Host) engine.findChildren()[0];
        }
 
        Host Host = neuer StandardHost();
        host.setName(Hostname);
        //Den übergeordneten und untergeordneten Container in Tomcat pflegen getEngine().addChild(host);
        Gastgeber zurückgeben;
    }

getEngine().addChild(host); Methode wählt den Aufruf der addChild-Methode in der übergeordneten Klasse containerBase aus

  @Überschreiben
    public void addChild(Container-Kind) {
        wenn (Globals.IS_SECURITY_ENABLED) {
            PrivilegierteAktion<Void> dp =
                neues PrivilegedAddChild(Kind);
            AccessController.doPrivileged(dp);
        } anders {
            //Der untergeordnete Parameter ist hier der Kontextcontainer addChildInternal(child);
        }
    }

Der Kerncode der Methode addChildInternal()

 private void addChildInternal(Container-Kind) {
 
        wenn( log.isDebugEnabled() )
            log.debug("Kind hinzufügen " + Kind + " " + dies);
        synchronisiert(Kinder) {
            wenn (Kinder.get(Kind.getName()) != null)
                neue IllegalArgumentException werfen("addChild: Child name '" +
                                                   Kind.getName() +
                                                   "' ist nicht eindeutig");
            child.setParent(this); // Kann IAE auslösen
            Kinder.put(Kind.getName(), Kind);
    }

4. Starten Sie den Container (tomcat.start())

4.1 Flussdiagramm für Methodenaufrufe

4.2 Quellcode-Analyse

Hinweis: StandardServer, StandardService, StandardEngine und andere Container erben alle LifecycleBase

Hier ist also die klassische Anwendung des Vorlagenmusters

1) Container Schicht für Schicht starten

Der Server entspricht zu diesem Zeitpunkt dem StandardServer, den wir zuvor erstellt haben

  öffentliche void start() wirft LifecycleException {
        //Verhindern, dass der Server-Container erstellt wird getServer();
        //Holen Sie sich den Connector-Container und setzen Sie ihn auf den Service-Container getConnector();
        //Die Implementierung von „Start“ wird hier in der Klasse LifecycleBase implementiert. //Die Methode LifecycleBase ist eine Vorlagenmethode, die im Tomcat-Startprozess sehr kritisch ist. server.start();
    }

2) Geben Sie die Startmethode ein

Geben Sie die Startmethode in LifecycleBase ein, wobei die Kernmethode startInternal ist.

Aus dem obigen wissen wir, dass wir jetzt die startInternal()-Methode des StandardServer-Containers aufrufen, also wählen wir hier StandardServer

Methodenpfad: org.apache.catalina.core.StandardServer.startInternal()

geschützt void startInternal() wirft LifecycleException {
 
        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
        setState(LifecycleState.STARTING);
 
        globalNamingResources.start();
 
        // Starten Sie unsere definierten Services
        synchronisiert (servicesLock) {
            //Starten Sie den Servicecontainer. In einem Tomcat können mehrere Servicecontainer konfiguriert werden. Jeder Servicecontainer entspricht einer unserer Serviceanwendungen für (Service service : services) {
                //Entspricht StandardService.startInternal()
                service.start();
            }
        }
    }

Aus dem obigen Code können wir ersehen, dass beim Starten des Servercontainers der Untercontainer-Servicecontainer gestartet werden muss. Von hier aus wird der Container Schicht für Schicht nach innen detoniert, sodass der nächste Schritt darin besteht, die Star-Methode jeder Containerschicht nacheinander aufzurufen. Ich werde hier nicht ins Detail gehen.

2) Der Kerncode der Methode startInternal() in ContainerBase, von der aus der StandardContext-Container gestartet wird

 // Starten Sie unsere untergeordneten Container, falls vorhanden
        //Hinzugefügt in der Methode addChild im Prozess addWwbapp, also müssen wir es hier finden //Was wir hier finden, ist der Kontextcontainer Container children[] = findChildren();
        Liste<Zukunft<Leer>> Ergebnisse = neue ArrayList<>();
        für (Container-Kind: Kinder) {
            //Starten Sie den Thread-Pool asynchron, um den Kontextcontainer zu starten und ein neues StartChild einzugeben
            Ergebnisse.Hinzufügen(startStopExecutor.submit(neues StartChild(Kind)));
        }

Die neue Methode StartChild(child) startet den StandardContext-Container

    private statische Klasse StartChild implementiert Callable<Void> {
 
        privates Container-Kind;
 
        öffentliches StartChild(Container-Unterelement) {
            dieses.Kind = Kind;
        }
 
        @Überschreiben
        öffentlicher Void call() wirft LifecycleException {
            //Starten Sie den Kontext, indem Sie eigentlich StandardContext.startInternal() aufrufen.
            Kind.Start();
            gibt null zurück;
        }
    }

Der Kerncode in der Methode StandardContext.startInternal():

   geschützt void fireLifecycleEvent(String-Typ, Objektdaten) {
        LifecycleEvent-Ereignis = neues LifecycleEvent(dieses, Typ, Daten);
        //lifecycleListeners Legen Sie im ersten Schritt der Methode addwebapp das Listening-ContextConfig-Objekt für (LifecycleListener listener : lifecycleListeners) { fest.
            //Die lifecycleEvent()-Methode von contextConfig wird hier aufgerufen listener.lifecycleEvent(event);
        }
    }

Geben Sie die Methode lifecycleEvent() in contextConfig ein

öffentliche void Lebenszyklusereignis(Lebenszyklusereignis-Ereignis) {
 
        // Identifizieren Sie den Kontext, mit dem wir verbunden sind
        versuchen {
            Kontext = (Kontext) event.getLifecycle();
        } Fang (ClassCastException e) {
            log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
            zurückkehren;
        }
 
        // Das aufgetretene Ereignis verarbeiten
        wenn (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
            //Schließen Sie die Inhaltsanalyse von web.xml ab configureStart();
        } sonst wenn (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
            vorStart();
        } sonst wenn (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
            // docBase für Verwaltungstools wiederherstellen
            wenn (originalDocBase != null) {
                Kontext.setDocBase(originalDocBase);
            }
        } sonst wenn (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
            konfigurierenStop();
        } sonst wenn (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
            init();
        } sonst wenn (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
            zerstören();
        }
 
    }

Bei der obigen Methode wird die Datei web.xml geladen und analysiert und das in der XML konfigurierte Servlet wird geladen und in ein Wrapper-Objekt gekapselt.

3) Starten Sie den Servlet-Container, Methode loadOnStartup(findChildren()) in StandardContext.startInternal()

öffentlicher Boolescher Wert loadOnStartup(Containerkinder[]) {
 
        // Sammeln Sie "Load on Startup"-Servlets, die initialisiert werden müssen
        TreeMap<Integer, ArrayList<Wrapper>> map = neues TreeMap<>();
        für (Container-Kind: Kinder) {
            //Der Wrapper ist hier das Servlet, das wir zuvor gekapselt haben
            Wrapper Wrapper = (Wrapper) Kind;
            int loadOnStartup = wrapper.getLoadOnStartup();
            wenn (loadOnStartup < 0) {
                weitermachen;
            }
            Integer-Schlüssel = Integer.valueOf(loadOnStartup);
            ArrayList<Wrapper> Liste = map.get(Schlüssel);
            wenn (Liste == null) {
                Liste = neue ArrayList<>();
                map.put(Schlüssel, Liste);
            }
            Liste.Hinzufügen(Wrapper);
        }
 
        // Laden Sie die gesammelten "Load on Startup"-Servlets
        für (ArrayList<Wrapper> Liste : map.values()) {
            für (Wrapper Wrapper: Liste) {
                versuchen {
                    //Die Lademethode ruft schließlich die Init-Methode des Servlets auf: wrapper.load();
                } Fang (ServletException e) {
                    getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
                          getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
                    // HINWEIS: Ladefehler (einschließlich eines Servlets, das
                    // UnavailableException von der init()-Methode) sind NICHT
                    // fatal für den Anwendungsstart
                    // sofern nicht failCtxIfServletStartFails="true" angegeben ist
                    wenn(getComputedFailCtxIfServletStartFails()) {
                        gibt false zurück;
                    }
                }
            }
        }
        gibt true zurück;
 
    }

Die Lademethode ruft schließlich die Init-Methode des Servlets auf.

V. Fazit

Der obige Inhalt beschreibt den Vorgang, wie der gesamte Tomcat die Servlet-Initialisierungsmethode aufruft. Dies ist mein Verständnis des gesamten Vorgangs. Wenn Fehler auftreten, korrigieren Sie mich bitte. Ich habe die wichtigen Teile des Quellcodes kommentiert. Wenn Sie ihn benötigen, können Sie meinen kommentierten Quellcode herunterladen. Die Adresse des kommentierten Quellcodes lautet:

https://github.com/good-jack/tomcat_source/tree/master

Damit ist dieser Artikel über die Analyse des Tomcat-Quellcodes zum Aufrufen der Servlet-Initialisierung abgeschlossen. Weitere Informationen zum Aufrufen der Servlet-Initialisierung durch Tomcat finden Sie in früheren Artikeln auf 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, Sie werden 123WORDPRESS.COM auch in Zukunft unterstützen!

Das könnte Sie auch interessieren:
  • Fallstricke bei der neueren Version des IDEA2021 Tomcat10-Servlets
  • Tiefgreifendes Verständnis der Erstellung und Implementierung von Servlets in Tomcat
  • Detaillierte Erklärung, wie Tomcat asynchrone Servlets implementiert
  • Servlet und Tomcat_PowerNode Java Academy
  • So implementieren Sie ein asynchrones Servlet in Tomcat
  • Einführung und Verwendung des Servlet-Objektpools in Tomcat
  • Eine detaillierte Einführung in den Arbeitsmechanismus von Servlet in Tomcat

<<:  Lösung für die leere Zeile vor der UTF-8-codierten Webseite, wenn sie Dateien enthält

>>:  Verwenden Sie vue3, um ein Mensch-Katze-Kommunikations-Applet zu implementieren

Artikel empfehlen

Detailliertes Beispiel einer MySQL-Austauschpartition

Detailliertes Beispiel einer MySQL-Austauschparti...

Grundlegende Implementierung der AOP-Programmierung in JavaScript

Einführung in AOP Die Hauptfunktion von AOP (Aspe...

SVG+CSS3 zum Erzielen eines dynamischen Welleneffekts

Eine Vektorwelle <svg viewBox="0 0 560 20...

So kapseln Sie die Karussellkomponente in Vue3

Zweck Kapseln Sie die Karussellkomponente und ver...

Sollte ich für das mobile Web-WAP Bootstrap oder jQuery Mobile verwenden?

Lösung des Problems Bootstrap ist ein CSS-Framewo...

Unterschied zwischen MySQL Btree-Index und Hash-Index

In MySQL werden die meisten Indizes (wie PRIMARY ...

Mehr als 100 Zeilen Code zur Implementierung von React Drag Hooks

Vorwort Der Quellcode umfasst insgesamt nur mehr ...

Eine kurze Diskussion zum Problem von Daten mit Nullwerten in der MySQL-Datenbank

Standardmäßig akzeptiert MySQL das Einfügen von 0...

MySQL 5.7.20 Zip-Installations-Tutorial

MySQL 5.7.20 Zip-Installation, der spezifische In...

Import-, Export-, Sicherungs- und Migrationsvorgänge für Docker-Images

Export: docker save -o centos.tar centos:latest #...