EinführungIm 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 TomcatNormalerweise 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-FrameworkBevor 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 MethodenaufrufeDas 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-Analyse1) 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 Methodenaufrufe4.2 Quellcode-AnalyseHinweis: 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. FazitDer 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:
|
<<: 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
Detailliertes Beispiel einer MySQL-Austauschparti...
Installieren Sie mysql5.7.18 auf CentOS6.7 1. Ent...
Einführung in AOP Die Hauptfunktion von AOP (Aspe...
Eine Vektorwelle <svg viewBox="0 0 560 20...
Zweck Kapseln Sie die Karussellkomponente und ver...
Lösung des Problems Bootstrap ist ein CSS-Framewo...
In MySQL werden die meisten Indizes (wie PRIMARY ...
Inhaltsverzeichnis Was ist ein Skelettsieb? Demo ...
Vorwort Der Quellcode umfasst insgesamt nur mehr ...
Vorwort Das Transaktionsdatenwörterbuch und das a...
Standardmäßig akzeptiert MySQL das Einfügen von 0...
Zunächst stellt sich häufig die Frage: Welche Bez...
MySQL 5.7.20 Zip-Installation, der spezifische In...
Export: docker save -o centos.tar centos:latest #...
Als ich kürzlich das Linux-Betriebssystem zum Aus...