1. Lernziele1.1. Beherrschen Sie das Design und die Prinzipien der Tomcat-Architektur, um Ihre internen Fähigkeiten zu verbessernMakroansicht Als „ Mikroskopische Ansicht Auch die heute beliebten Microservices folgen dieser Idee und teilen die monolithische Anwendung entsprechend ihrer Funktionen in „Microservices“ auf. Beim Aufteilen werden die Gemeinsamkeiten extrahiert, und diese Gemeinsamkeiten werden zu den zentralen Basisdiensten oder allgemeinen Bibliotheken. Dasselbe gilt für die Idee einer „mittleren Plattform“. Entwurfsmuster sind oft ein leistungsstarkes Werkzeug zum Einkapseln von Änderungen. Der sinnvolle Einsatz von Entwurfsmustern kann unseren Code und unser Systemdesign elegant und übersichtlich gestalten. Dies ist die „innere Stärke“, die durch das Erlernen hervorragender Open-Source-Software gewonnen werden kann. Sie wird niemals veraltet sein und die darin enthaltenen Designideen und -philosophien sind der grundlegende Weg. Lernen Sie aus ihren Designerfahrungen, verwenden Sie Designmuster sinnvoll, um Änderungen und Konstanten zu kapseln, und nutzen Sie die Erfahrungen aus ihrem Quellcode, um Ihre eigenen Systemdesignfähigkeiten zu verbessern. 1.2. Makroverständnis der Verbindung einer Anfrage mit SpringIm Laufe unserer Arbeit sind wir bereits sehr gut mit der Java-Syntax vertraut, haben uns sogar einige Entwurfsmuster „gemerkt“ und viele Web-Frameworks verwendet, aber wir haben selten die Gelegenheit, sie in tatsächlichen Projekten zu verwenden. Das eigenständige Entwerfen eines Systems scheint nur die Implementierung eines Dienstes nach Bedarf zu sein. Ich habe anscheinend keinen umfassenden Überblick über die Java-Webentwicklung. Ich weiß beispielsweise nicht, wie die Browseranforderung mit dem Code in Spring verknüpft ist. Um diesen Engpass zu überwinden, könnten Sie sich auf die Schultern von Riesen stellen, hervorragende Open-Source-Systeme kennenlernen und sehen, wie die Großen über diese Probleme denken. Nachdem ich die Prinzipien von Tomcat studiert hatte, stellte ich fest, dass die 1.3. Verbessern Sie Ihre SystemdesignfähigkeitenBeim Erlernen von Tomcat habe ich auch festgestellt, dass ich viele fortgeschrittene Java-Technologien verwendet habe, wie etwa parallele Multithread-Programmierung in Java, Socket-Netzwerkprogrammierung und Reflexion. Früher kannte ich diese Technologien nur und hatte einige Fragen für Vorstellungsgespräche auswendig gelernt. Aber ich habe immer das Gefühl, dass es eine Lücke zwischen „Wissen“ und der Fähigkeit gibt, es zu nutzen. Durch das Studium des Tomcat-Quellcodes habe ich gelernt, in welchen Szenarien diese Technologien eingesetzt werden können. Es gibt auch Systemdesignfunktionen wie schnittstellenorientierte Programmierung, komponentenbasierter Kombinationsmodus, Skelett-abstrakte Klasse, Starten und Stoppen mit einem Klick, Objektpooltechnologie und verschiedene Designmuster wie Vorlagenmethode, Beobachtermodus, Verantwortungskettenmodus usw. Später begann ich, sie nachzuahmen und diese Designideen in der tatsächlichen Arbeit anzuwenden. 2. GesamtarchitekturentwurfHeute werden wir die Designideen von Tomcat Schritt für Schritt analysieren. Einerseits können wir die Gesamtarchitektur von Tomcat kennenlernen, lernen, wie man ein komplexes System aus einer Makroperspektive entwirft, wie man Module der obersten Ebene entwirft und die Beziehung zwischen Modulen. Andererseits legt es auch die Grundlage für unser eingehendes Studium der Arbeitsprinzipien von Tomcat. Tomcat-Startvorgang:
Tomcat implementiert zwei Kernfunktionen:
Daher ist Tomcat mit zwei Kernkomponenten konzipiert: Connector und Container. Der Connector ist für die externe Kommunikation verantwortlich und der Container für die interne Verarbeitung Um mehrere
Jede Komponente hat einen entsprechenden Lebenszyklus und muss gestartet werden, und ihre internen Unterkomponenten müssen ebenfalls gestartet werden. Beispielsweise enthält eine Tomcat-Instanz einen Dienst, und ein Dienst enthält mehrere Konnektoren und einen Container. Ein Container enthält mehrere Hosts, und innerhalb des Hosts können sich mehrere Contex-Container befinden, und ein Kontext kann auch mehrere Servlets enthalten. Daher verwendet Tomcat den Composite-Modus zum Verwalten der einzelnen Komponenten und behandelt jede Komponente als eine einzelne Gruppe. Insgesamt gleicht das Design der einzelnen Komponenten einer „russischen Puppe“. 2.1 Anschlüsse Bevor ich über Konnektoren spreche, möchte ich zunächst die Grundlagen für die verschiedenen Die von
Die von Tomcat unterstützten Anwendungsschichtprotokolle sind:
Somit kann ein Container an mehreren Konnektoren andocken. Der Connector schützt den Die funktionalen Anforderungen an den verfeinerten Steckverbinder sind:
Nachdem die Anforderungen klar aufgelistet sind, müssen wir uns als nächstes die Frage stellen, welche Untermodule der Connector haben soll. Ein ausgezeichnetes modulares Design sollte eine hohe Kohäsion und geringe Kopplung berücksichtigen.
Wir haben festgestellt, dass Konnektoren drei eng miteinander verknüpfte Funktionen erfüllen müssen:
Daher haben die Entwickler von Tomcat drei Komponenten entwickelt, um diese drei Funktionen zu implementieren, nämlich Das E/A-Modell der Netzwerkkommunikation ändert sich, und auch das Protokoll der Anwendungsschicht ändert sich, aber die allgemeine Verarbeitungslogik bleibt unverändert. 2.2 Kapselungsänderungen und Invarianz Aus diesem Grund hat Tomcat eine Reihe abstrakter Basisklassen entwickelt, um diese stabilen Teile zu kapseln. Die abstrakte Basisklasse Dies ist die Anwendung des Template-Method-Entwurfsmusters. Zusammenfassend lässt sich sagen, dass die drei Kernkomponenten des Connectors, ProtocolHandler-Komponente: Es verwaltet hauptsächlich Netzwerkverbindungen und Protokolle der Anwendungsschicht. Es umfasst zwei wichtige Komponenten: EndPoint und Processor. Die beiden Komponenten werden kombiniert, um ProtocoHandler zu bilden. Lassen Sie mich ihre Arbeitsprinzipien im Detail vorstellen. Endpunkt: Der Acceptor wird verwendet, um die Socket-Verbindungsanforderung zu überwachen. Wir wissen, dass die Verwendung von Java-Multiplexern nichts weiter als zwei Schritte umfasst:
In Tomcat ist LimitLatch ist ein Verbindungscontroller, der die maximale Anzahl von Verbindungen steuert. Der Standardwert im NIO-Modus beträgt 10.000. Wenn dieser Schwellenwert erreicht ist, wird die Verbindungsanforderung abgelehnt. Die Essenz von SocketProcessor implementiert die Runnable-Schnittstelle, in Der Arbeitsablauf ist wie folgt: Prozessor: Der Prozessor wird zur Implementierung des HTTP-Protokolls verwendet. Der Prozessor empfängt den Socket vom Endpunkt, liest den Byte-Stream, analysiert ihn in Tomcat-Anforderungs- und Antwortobjekte und übermittelt ihn zur Verarbeitung über den Adapter an den Container. Der Prozessor ist eine Abstraktion des Anwendungsschichtprotokolls. Aus der Abbildung können wir ersehen, dass EndPoint, nachdem es die Socket-Verbindung empfangen hat, eine SocketProcessor-Aufgabe generiert und diese zur Verarbeitung an den Thread-Pool übermittelt. Die Run-Methode von SocketProcessor ruft die HttpProcessor-Komponente auf, um das Protokoll der Anwendungsschicht zu analysieren. Nachdem der Prozessor durch Analyse das Request-Objekt generiert hat, ruft er die Service-Methode des Adapters auf. Die Methode übergibt die Anforderung über den folgenden Code an den Container. // Aufruf des Containers connector.getService().getContainer().getPipeline().getFirst().invoke(Anfrage, Antwort); Adapterkomponente: Aufgrund unterschiedlicher Protokolle definiert Tomcat seine eigene Die Lösung der Tomcat-Designer besteht in der Einführung 2.3 Behälter Der Connector ist für die externe Kommunikation und der Container für die interne Verarbeitung verantwortlich. Insbesondere übernimmt der Connector die Analyse der Socket-Kommunikation und des Anwendungsschichtprotokolls, um Container: Wie der Name schon sagt, wird er zum Aufbewahren von Dingen verwendet, daher wird der Tomcat-Container zum Laden Tomcat hat vier Container entwickelt: Es ist zu beachten, dass diese vier Container nicht in einer parallelen Beziehung stehen, sondern in einer Eltern-Kind-Beziehung, wie in der folgenden Abbildung dargestellt: Sie fragen sich vielleicht, warum wir so viele Containerebenen entwerfen müssen? Erhöht das nicht die Komplexität? Der Grund hierfür liegt in der Tatsache, dass Tomcat eine Schichtenarchitektur verwendet, um den Servlet-Container sehr flexibel zu machen. Denn hier kommt es vor, dass ein Host mehrere Kontexte hat und ein Kontext auch mehrere Servlets enthält und jede Komponente ein einheitliches Lebenszyklusmanagement erfordert, sodass der kombinierte Modus diese Container entwirft Sie können die Tomcat-Konfigurationsdatei verwenden, um ein tieferes Verständnis der hierarchischen Beziehungen zu erlangen. <Server port="8005" shutdown="SHUTDOWN"> // Komponente der obersten Ebene, kann mehrere Dienste enthalten, stellt eine Tomcat-Instanz dar<Service name="Catalina"> // Komponente der obersten Ebene, enthält eine Engine, mehrere Konnektoren<Connector port="8080" protocol="HTTP/1.1" VerbindungsTimeout="20000" UmleitungsPort="8443" /> <!-- Definieren Sie einen AJP 1.3-Connector auf Port 8009 --> <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> // Connector // Containerkomponente: Eine Engine verarbeitet alle Serviceanfragen, einschließlich mehrerer Hosts <Engine-Name="Catalina" Standardhost="localhost"> //Containerkomponente: verarbeitet Clientanforderungen unter dem angegebenen Host, der mehrere Kontexte enthalten kann <Hostname="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> //Containerkomponente: verarbeitet alle Clientanforderungen für eine bestimmte Context-Web-Anwendung <Context></Context> </Host> </Engine> </Dienst> </Server> Wie verwalte ich diese Container? Wir haben festgestellt, dass zwischen Containern eine Eltern-Kind-Beziehung besteht, die eine Baumstruktur bildet. Ist es möglich, sich das Kombinationsmuster im Entwurfsmuster vorzustellen? Tomcat verwendet den kombinierten Modus, um diese Container zu verwalten. Die spezifische Implementierungsmethode besteht darin, dass alle Containerkomponenten die öffentliche Schnittstelle Container erweitert Lebenszyklus { öffentliche void setName(String name); öffentlicher Container getParent(); öffentliche void setParent(Container Container); öffentliche void addChild(Container-Unterelement); öffentliche void removeChild(Container-Unterelement); öffentlicher Container findChild(Stringname); } Wir haben Methoden wie 2.4. Der Prozess der Lokalisierung des Servlets Wie gelangt eine Anfrage zu welchem Die Funktion Wenn eine Anforderung eingeht, kann Wenn ein Benutzer eine URL wie beispielsweise 1. Bestimmen Sie zunächst den Dienst und die Engine anhand des Protokolls und der Portnummer. Der Standard-HTTP-Connector von Tomcat lauscht auf Port 8080 und der Standard-AJP-Connector lauscht auf Port 8009. Die URL im obigen Beispiel greift auf Port 8080 zu, sodass die Anforderung vom HTTP-Connector empfangen wird. Da ein Connector zu einer Servicekomponente gehört, wird die Servicekomponente bestimmt. Wir wissen auch, dass eine Servicekomponente zusätzlich zu mehreren Konnektoren auch eine Containerkomponente hat, und zwar einen Engine-Container. Sobald also der Service bestimmt ist, ist auch die Engine bestimmt. 2. Wählen Sie den Host basierend auf dem Domänennamen aus. Nachdem Service und Engine ermittelt wurden, sucht die Mapper-Komponente über den Domänennamen in der URL nach dem entsprechenden Host-Container. Der Domänenname, auf den im Beispiel über die URL zugegriffen wird, lautet beispielsweise 3. Suchen Sie die Kontextkomponente anhand des URL-Pfads. Nachdem der Host ermittelt wurde, gleicht der Mapper den Pfad der entsprechenden Webanwendung entsprechend dem URL-Pfad ab. In diesem Beispiel lautet der aufgerufene Pfad beispielsweise /order, sodass der Kontextcontainer Context4 gefunden wird. 4. Suchen Sie den Wrapper (Servlet) basierend auf dem URL-Pfad. Nachdem der Kontext bestimmt wurde, findet der Mapper den spezifischen Wrapper und das Servlet gemäß dem in web.xml konfigurierten Servlet-Mapping-Pfad. Der Adapter im Connector ruft die Servicemethode des Containers auf, um das Servlet auszuführen. Der erste Container, der die Anforderung empfängt, ist der Engine-Container. Nachdem der Engine-Container die Anforderung verarbeitet hat, übergibt er sie zur weiteren Verarbeitung an seinen untergeordneten Container Host und so weiter. Schließlich wird die Anforderung an den Wrapper-Container übergeben, und der Wrapper ruft das endgültige Servlet zur Verarbeitung auf. Wie wird dieser Aufrufvorgang implementiert? Die Antwort ist die Verwendung der Pipeline-Valve-Pipeline. öffentliche Schnittstelle Valve { öffentliches Valve getNext(); öffentliche void setNext(Ventil Ventil); public void invoke (Anfrage Anfrage, Antwort Antwort) } Schauen Sie sich weiterhin die Pipeline-Schnittstelle an öffentliche Schnittstelle Pipeline { öffentliche void addValve(Ventil Ventil); öffentliches Valve getBasic(); öffentliche void setBasic(Ventil Ventil); öffentliches Valve getFirst(); } Es gibt Tatsächlich hat jeder Container ein Pipeline-Objekt. Solange das erste Ventil dieser Pipeline ausgelöst wird, werden alle Ventile in Dies liegt daran, dass es in Der gesamte Vorgang wird durch @Überschreiben öffentlicher void-Dienst(org.apache.coyote.Request req, org.apache.coyote.Response res) { // Anderen Code weglassen // Aufruf des Containers Connector.getService().getContainer().getPipeline().getFirst().aufrufen( Anfrage, Antwort); ... } Das letzte Ventil des Wrapper-Containers erstellt eine Filterkette und ruft Haben wir nicht vorher über
Lebenszyklus Zuvor haben wir gesehen, dass Wie kann die Erstellung, Initialisierung, der Start, Stopp und die Zerstörung von Komponenten einheitlich verwaltet werden? Wie kann man die Code-Logik deutlich machen? Wie können Komponenten einfach hinzugefügt oder entfernt werden? Wie kann sichergestellt werden, dass Komponenten ohne Auslassungen oder Duplikate gestartet und gestoppt werden? Starten und Stoppen mit nur einem Tastendruck: LifeCycle-Schnittstelle Beim Design geht es darum, die veränderlichen und unveränderlichen Punkte des Systems zu finden. Der unveränderliche Punkt hierbei ist, dass jede Komponente die Prozesse der Erstellung, Initialisierung und des Starts durchlaufen muss und diese Zustände und Zustandstransformationen unverändert bleiben. Die Änderung besteht darin, dass die Initialisierungsmethode jeder spezifischen Komponente, d. h. die Startmethode, unterschiedlich ist. Daher abstrahiert Tomcat die invarianten Punkte in eine Schnittstelle, die sich auf den Lebenszyklus bezieht und LifeCycle genannt wird. Die LifeCycle-Schnittstelle definiert mehrere Methoden: In Skalierbarkeit: Lebenszyklus-Ereignisse Betrachten wir ein weiteres Problem, nämlich die Skalierbarkeit des Systems. Weil die spezifische Implementierung der Nachfolgend sehen Sie die Definition der Wiederverwendbarkeit: LifeCycleBase abstrakte Basisklasse Sehen Sie sich das Entwurfsmuster „Abstrakte Vorlage“ noch einmal an. Bei der Schnittstelle müssen wir Klassen verwenden, um die Schnittstelle zu implementieren. Im Allgemeinen gibt es mehr als eine Implementierungsklasse, und verschiedene Klassen verwenden bei der Implementierung der Schnittstelle häufig die gleiche Logik. Wenn jede Unterklasse sie implementieren muss, entsteht doppelter Code. Wie können Unterklassen diese Logik wiederverwenden? Tatsächlich geht es darum, eine Basisklasse zu definieren, um eine gemeinsame Logik zu implementieren, und diese dann jeder Unterklasse erben zu lassen, um den Zweck der Wiederverwendung zu erreichen. Tomcat definiert eine Basisklasse LifeCycleBase zur Implementierung der LifeCycle-Schnittstelle und fügt der Basisklasse einige allgemeine Logik hinzu, wie etwa den Übergang und die Aufrechterhaltung von Lebenszuständen, das Auslösen von Lebensereignissen und das Hinzufügen und Löschen von Listenern usw., während die Unterklasse für die Implementierung ihrer eigenen Initialisierungs-, Start- und Stoppmethoden verantwortlich ist. öffentliche abstrakte Klasse LifecycleBase implementiert Lifecycle{ //Alle Beobachter privat halten final List<LifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<>(); /** * Veranstaltung veröffentlichen * * @param Typ Ereignistyp * @param data Mit dem Ereignis verknüpfte Daten. */ geschützt void fireLifecycleEvent(String-Typ, Objektdaten) { LifecycleEvent-Ereignis = neues LifecycleEvent(dieses, Typ, Daten); für (LifecycleListener-Listener: LifecycleListeners) { listener.lifecycleEvent(Ereignis); } } // Die Template-Methode definiert den gesamten Startvorgang und startet alle Container @Override öffentliches final synchronisiertes void init() wirft LifecycleException { //1. Statusprüfung if (!state.equals(LifecycleState.NEW)) { ungültiger Übergang (Lifecycle.BEFORE_INIT_EVENT); } versuchen { //2. Lösen Sie den Listener für das INITIALIZING-Ereignis aus: setStateInternal(LifecycleState.INITIALIZING, null, false); // 3. Rufen Sie die Initialisierungsmethode initInternal() der spezifischen Unterklasse auf; // 4. Lösen Sie den Listener für das INITIALIZED-Ereignis aus: setStateInternal(LifecycleState.INITIALIZED, null, false); } fangen (Wurfbares t) { ExceptionUtils.handleThrowable(t); setStateInternal(LifecycleState.FAILED, null, false); neue LifecycleException werfen( sm.getString("lifecycleBase.initFail",toString()), t); } } } Um ein Klick zu erreichen, berücksichtigt Tomcat die Skalierbarkeit Wenn Sie eine Reihe von Entitäten mit Eltern-Kind-Beziehungen beibehalten müssen, sollten Sie das zusammengesetzte Muster verwenden. Das Observer-Muster klingt "High-End", aber tatsächlich bedeutet dies, dass bei einem Ereignis eine Reihe von Aktualisierungsvorgängen durchgeführt werden muss. Ein Mechanismus mit niedriger Kopplung, nicht störender Benachrichtigung und Aktualisierung wird implementiert. 3. Warum Tomcat den übergeordneten Delegationsmechanismus bricht3.1 Wir wissen, dass öffentliche Klasse<?> loadClass(String name) löst ClassNotFoundException { aus gibt loadClass(Name, false) zurück; } geschützte Klasse<?> loadClass(String-Name, Boolesche Auflösung) löst ClassNotFoundException aus { synchronisiert (getClassLoadingLock(name)) { // Herausfinden, ob die Klasse geladen wurde Class<?> c = findLoadedClass(name); // Wenn nicht geladen if (c == null) { // Delegieren Sie den übergeordneten Loader zum Laden und rufen Sie rekursiv auf, wenn (übergeordnet! = Null) { c = übergeordnet.loadClass (Name, false); } anders { // Wenn der übergeordnete Loader leer ist, finden Sie heraus, ob Bootstrap geladen wurde. } // Wenn es noch nicht geladen werden kann, rufen Sie Ihre eigene FindClass an, um zu laden, wenn (c == null) { c = Klasse finden(Name); } } if (Resolve) { Resolveclass (c); } Rückkehr c; } } geschützte Klasse<?> findClass(Stringname){ //1. Suchen Sie gemäß dem übergebenen Klassennamen nach der Klassendatei in einem bestimmten Verzeichnis und lesen Sie die .class-Datei in den Speicher ein ... //2. Rufen Sie defineClass auf, um das Byte-Array in ein Class-Objekt umzuwandeln return defineClass(buf, off, len); } // Analysieren Sie das Bytecode-Array in ein Class-Objekt und implementieren Sie es mit nativen Methoden protected final Class<?> defineClass(byte[] b, int off, int len){ ... } Es gibt 3 Klassenlader in JDK, und Sie können auch Klassenlader anpassen.
Das Funktionsprinzip dieser Klassenlader ist dasselbe. Der Unterschied besteht darin, dass ihre Ladepfade unterschiedlich sind, d. h. die von der Methode 3.2 Tomcat führt im Wesentlichen regelmäßige Aufgaben über einen Hintergrund -Thread aus, wodurch regelmäßig Änderungen in Klassendateien erfasst und Klassen neu geladen werden, wenn Änderungen festgestellt werden. Schauen wir uns an, wie geschützte KlassencontainerbackgroundProcessor implementiert Runnable { @Überschreiben öffentliche Leere ausführen() { // Bitte beachten Sie, dass der hier übergebene Parameter die Instanz der "Host Class" -Prozessschilds (Containerbase.This) ist; } geschützte void ProcessChildren (Containerbehälter) { versuchen { // 1. Container.BackgroundProcess (); // 2. // Auf diese Weise werden alle Nachkommen des aktuellen Containers verarbeitet Container [] Kinder = Container.FindChildren (); für (int i = 0; i <children.length; i ++) { // Bitte beachten Sie hier, dass die Containerbasisklasse eine Variable namens Hintergrundprozessordelay hat. if (Kinder [i] .GetackgroundProcessordelay () <= 0) { processCildren (Kinder [i]); } } } catch (Throwable t) {...} Die heiße Belastung von Tomcat wird im Kontextcontainer implementiert, hauptsächlich durch Aufrufen der Reload -Methode des Kontextcontainers. Abgesehen von den Details aus makroischer Sicht sind die Hauptaufgaben wie folgt:
In diesem Prozess spielen Klassenlader eine Schlüsselrolle. Ein Kontextbehälter entspricht einem Klassenlader. Während des Startprozesses erstellt der Kontext -Container einen neuen Klassenloader zum Laden neuer Klassendateien. 3.3 Tomcat's Custom Class Loader FindClass -Methode
Um das Verständnis und das Lesen zu erleichtern, habe ich einige Details entfernt: öffentliche Klasse<?> findClass(String name) löst ClassNotFoundException { aus ... Klasse<?> clazz = null; versuchen { //1. Suchen Sie zuerst im Web-Anwendungsverzeichnis nach der Klasse clazz = findClassInternal(name); } Fang (RuntimeException e) { werfen e; } wenn (clazz == null) { versuchen { //2. Wenn im lokalen Verzeichnis nicht gefunden, lassen Sie den übergeordneten Loader suchen clazz = super.findClass(name); } Fang (RuntimeException e) { werfen e; } //3. Wenn die übergeordnete Klasse nicht gefunden wird, werfen Sie ClassNotFoundException wenn (clazz == null) { wirf eine neue ClassNotFoundException(Name); } Rückgabeklazz; } 1. Suchen Sie zuerst nach der Klasse, die im lokalen Verzeichnis der Webanwendung geladen wird. 2. Wenn nicht gefunden wird, wird es 3. Wenn der übergeordnete Loader die Klasse auch nicht finden kann, wird eine Lastklasse -Methode Schauen wir uns die Implementierung öffentliche Klasse<?> loadClass(Stringname, Boolesche Auflösung) wirft ClassNotFoundException { synchronisiert (getClassLoadingLock(name)) { Klasse<?> clazz = null; //1. Prüfen Sie zunächst im lokalen Cache, ob die Klasse geladen wurde. clazz = findLoadedClass0(name); if (clazz != null) { wenn (auflösen) Klasse auflösen(clazz); Rückgabeklazz; } // 2. if (clazz != null) { wenn (auflösen) Klasse auflösen(clazz); Rückgabeklazz; } // 3. Versuchen Sie, die Klasse mit dem Klassenlader ExtClassLoader zu laden. Warum? Ich habe versucht, den ClassLoader zu starten, aber ich habe ihn nicht gestartet. versuchen { clazz = javaseLoader.loadClass(name); if (clazz != null) { wenn (auflösen) Klasse auflösen(clazz); Rückgabeklazz; } } Fang (ClassNotFoundException e) { // Ignorieren } // 4. Versuche die Klasse im lokalen Verzeichnis zu suchen und zu laden try { clazz = Klasse finden(Name); if (clazz != null) { wenn (auflösen) Klasse auflösen(clazz); Rückgabeklazz; } } Fang (ClassNotFoundException e) { // Ignorieren } // 5. Versuchen Sie, den Systemklassenlader (d. h. AppClassLoader) zum Laden zu verwenden try { clazz = Klasse.forName(Name, falsch, übergeordnetes Element); if (clazz != null) { wenn (auflösen) Klasse auflösen(clazz); Rückgabeklazz; } } Fang (ClassNotFoundException e) { // Ignorieren } } //6. Das Laden der oben genannten Prozesse schlägt fehl und es wird eine Ausnahme ausgelöst: throw new ClassNotFoundException(name); } Es gibt sechs Hauptschritte: 1. Überprüfen Sie zuerst den lokalen Cache, ob die Klasse geladen wurde, dh, ob der Klassenlader von Tomcat diese Klasse geladen hat. 2. Wenn der Tomcat -Klasse -Loader diese Klasse nicht geladen hat, prüfen Sie, ob der Lader der Systemklasse sie geladen hat. 3. Wenn keiner von ihnen existiert, laden Sie es aus. Da Tomcat den übergeordneten Delegationsmechanismus brechen muss, wird die Objektklasse zuerst in der Webanwendung angepasst, wenn diese 4. Wenn der 5. Wenn die Klasse im lokalen Verzeichnis nicht vorhanden ist, bedeutet dies, dass es sich nicht um eine Klasse handelt, die von der Webanwendung selbst definiert ist, und wird vom Systemklassenlader geladen. Bitte beachten Sie hierbei, dass die Übergabe der Web-Anwendung an den Systemklassenlader durch 6. Wenn alle oben genannten Ladeprozesse fehlschlagen, wird eine 3.4. Tomcat ist Angenommen, wir führen zwei Webanwendungen in Tomcat aus, und in den beiden 2. Wenn zwei Webanwendungen von 3. Wie der JVM müssen wir die Klassen von Tomcat selbst und die Klassen der Webanwendung isolieren. 1. WebAppClassloader Die Lösung von Tomcat besteht darin, einen Klassenloader 2. SharedClassloader Die wesentliche Anforderung ist, wie Sie Bibliotheksklassen zwischen zwei Webanwendungen freigeben und dieselbe Klasse nicht wiederholt laden. Im übergeordneten Delegationsmechanismus kann jeder untergeordnete Lader Klassen durch den übergeordneten Lader laden. Ist es also nicht ausreichend, die Klassen zu setzen, die unter den Ladeweg des übergeordneten Laders geteilt werden müssen? Daher fügten die Designer von Tomcat einen Class Loader 3. Catalinacklassloader Wie isolieren Sie die eigenen Klassen von Tomcat aus den Klassen der Webanwendung? Das Teilen kann durch eine Eltern-Kind-Beziehung erfolgen, während Isolation eine brüderliche Beziehung erfordert. Es gibt ein Problem mit diesem Design. Die alte Methode besteht darin, einen weiteren 4. Zusammenfassung der allgemeinen Architektur -DesignanalyseDurch die vorherige Studie über die Gesamtarchitektur von Tomcat wissen wir, welche Kernkomponenten Tomcat und die Beziehung zwischen Komponenten haben. Und wie Tomcat eine HTTP -Anfrage umgeht. Lassen Sie es sich durch ein vereinfachtes Klassendiagramm aus dem Diagramm überprüfen. 4.1 Anschlüsse Die Gesamtarchitektur von Tomcat besteht aus zwei Kernkomponenten: Stecker und Behälter. Der Anschluss ist für die externe Kommunikation verantwortlich und der Container ist für die interne Verarbeitung verantwortlich. Durch das Studium der Gesamtarchitektur von Tomcat können wir einige grundlegende Ideen für die Gestaltung komplexer Systeme erhalten. Zunächst müssen wir die Anforderungen analysieren und die Submodule basierend auf dem Prinzip der hohen Kohäsion und der niedrigen Kopplung ermitteln. 4.2 BehälterDer kombinierte Modus wird zum Verwalten des Containers verwendet, und die Start-Ereignisse werden über den Observer-Modus veröffentlicht, um Entkopplung und offene Prinzipien zu erreichen. Das skelett abstrakte Klassen- und Template -Methode abstrakte Änderungen und Konstanten, und die Änderungen werden den Unterklassen zur Implementierung überlassen, wodurch die Wiederverwendung von Code und eine flexible Expansion erreicht werden. Verwenden Sie den Ansatz der Verantwortungskette, um Anfragen wie Protokollierung zu behandeln. 4.3 Klassenlader Die benutzerdefinierte Klassenlader 5. Praktische AnwendungsszenarienDas allgemeine Architekturdesign von Tomcat wird kurz analysiert, von [Anschlüssen] bis [Container], und die Designideen und Designmuster einiger Komponenten werden ausführlich erläutert. Der nächste Schritt ist, wie Sie das, was Sie gelernt haben, anwenden und aus dem eleganten Design lernen und auf die tatsächliche Arbeitsentwicklung anwenden. Lernen beginnt mit Nachahmung. 5.1.Bei der Arbeit besteht die Forderung, dass Benutzer einige Informationen eingeben und die [industriellen und kommerziellen Informationen des Unternehmens], [gerichtliche Informationen], [China Registrierungsstatus] usw., eine oder mehrere Module wie unten gezeigt, überprüfen können, und es gibt einige gemeinsame Dinge zwischen Modulen, die von jedem Modul wiederverwendet werden müssen. Dies ist wie eine Anfrage, die von mehreren Modulen verarbeitet wird. Daher können wir jedes Abfragemodul in ein Verarbeitungsventil abstrahieren und eine Liste auf diese Weise speichern. Der spezifische Beispielcode lautet wie folgt: Zunächst sind wir unser /** * Kette des Verantwortungsmusters: Behandeln Sie jedes Modulventil */ öffentliche Schnittstelle Valve { /** * Rufen Sie * @param netcheckdto an */ void Invoke (netcheckdto netcheckdto); } Definieren Sie abstrakte Basisklassen, um Code wiederzuverwenden. public abstract class AbstractCheckvalve implementiert Valve { öffentliche endgültige Analyse -Reportlogdo getLatesthistoryData (netcheckdto netcheckdto, netcheckDatatypeenum checkDatatypeenum) { // Historienaufzeichnungen abrufen, Codelogik weggelassen} // Erhalten Sie die Konfiguration der Verifizierungsdatenquelle im Publikum Final String getModulesource (String QuerySource, Moduleenum moduleenum) { // Code -Logik auslassen} } Definieren Sie die Geschäftslogik jedes Moduls, z. B. die Verarbeitung von [Baidu Negative News] @Slf4j @Service öffentliche Klasse BaidunegativeValve erweitert AbstractCheckvalve { @Überschreiben public void Invoke (netcheckdto netcheckdto) { } } Der letzte Schritt besteht darin, die Module zu verwalten, die Benutzer überprüfen möchten, und wir speichern sie überlist. Wird verwendet, um das erforderliche Inspektionsmodul auszulösen @Slf4j @Service öffentliche Klasse NetcheckService { // Injizieren Sie alle Ventile @autowired private Karte <String, Ventil> Valvemap; /** * Überprüfungsanforderung senden * * @Param netcheckdto */ @ASync ("asyncexecutor") public void sendCheckRequest (netcheckdto netcheckdto) { // Modulventile zum Speichern von List für Kundenauswahl <ventils> Ventile = New ArrayList <> (); CheckModuleConfigDTO checkModuleConfig = netcheckdto.getCheckmoduleConfig (); // Fügen Sie das vom Benutzer ausgewählte Modul zur Ventilkette hinzu, wenn (checkModuleConfig.getBaidunegative ()) { valves.add (valvemap.get ("baidunegativeValve"); } // einen Code weglassen ....... if (collectionUtils.isempty (Ventile)) { log.info ("Das Netzwerkinspektionsmodul ist leer, es gibt keine Aufgabe zu prüfen"); zurückkehren; } // Auslöser Verarbeitungsventile.foreach (Ventil -> Ventil.Invoke (netcheckdto)); } } 5.2 Template -MethodenmusterDie Anforderung besteht darin, eine Analyse des Finanzberichts auf der Grundlage des vom Kunden eingegebenen Finanzberichts Excel -Daten oder Firmennamen durchzuführen. Überprüfen Sie bei nicht gelisteten Produkten Excel ->, ob die Daten legal sind -> Berechnungen durchführen. Listete Unternehmen: Stellen Sie fest, ob der Name vorhanden ist. Die wichtige "Änderung" und "Unchange"
Der gesamte Algorithmusprozess ist eine feste Vorlage, aber die spezifische Implementierung einiger Änderungen im Algorithmus muss in verschiedene Unterklassen verschoben werden. öffentliche abstrakte Klasse Abstractanalysistemplate { /** * Senden Sie die Analyse -Analyse -Vorlagenmethode und definieren Sie den Skelettprozess * @Param ReportanalysisRequest * @zurückkehren */ öffentliche Final FinancialanalyseResultdto Doprocess (FinancialReportanalysisRequest ReportanalyseRequest) { FinancialanalysisResultdto AnalysisDTO = neue FinanzanalyseResultdto (); // Zusammenfassung log.info ("prepevalidat validierungsergebnis = {}", prepevalidat); if (! prepevalidat) { // Abstract -Methode: Erstellen Sie die für Benachrichtigungs -E -Mails erforderlichen Daten BuildemailData (AnalysisDTO); log.info ("E -Mail -Informationen erstellen, data = {}", json.tojonstring (AnalysisDto)); Return Analysisdto; } String reportno = finanziell_report_no_prefix + reportanalysisRequest.getUerid () + serialnumGenerator.getFixlenthSerialNumber (); // Analyse log initfinancialanalysislog (reportanalysisRequest, ReportNo) generieren; // Analyse -Aufzeichnung initanalysisreport (reportanalysisRequest, ReportNo) generieren; versuchen { // Abstract -Methode: Daten in Finanzbericht ziehen, verschiedene Unterklassen implementieren finanzialdatadto finanzialdata = pullfinancialdata (ReportanalysisRequest); log.info ("Finanzberichtsdaten abgeschlossen, Bereits zur Durchführung von Berechnungen"); // Berechnungsindikatoren FinancialCalccontext.calc (ReportanalysisRequest, Financialdata, ReportNo); // das Analyse -Protokoll auf Success SuccessCalc (ReportNo) festlegen; } Fang (Ausnahme e) { log.Error ("Eine Ausnahme in der Berechnung der Finanzberichtsberechnung", e); // das Analysebotium auf failCalc (ReportNo) festlegen; werfen e; } Return Analysisdto; } } Erstellen Sie schließlich zwei Unterklassen, um die Vorlage zu erben und die abstrakte Methode zu implementieren. Dadurch entkoppelt sich die Verarbeitungslogik der aufgelisteten und nicht gelisteten Typen, während der Code wiederverwendet. 5.3 StrategiemusterDie Anforderung besteht darin, eine Excel -Schnittstelle zu erstellen, die die allgemeinen Bankaussagen identifizieren kann. Jetzt analysieren wir den Index des Excel -Headers, in dem sich jedes erforderliche Feld befindet. Aber es gibt viele Situationen des Wasserflusses: 1. Eine soll alle Standardfelder einbeziehen. 2. Die Einschüsse des Einkommens und der Ausgaben sind in derselben Spalte und Einkommen und Ausgaben werden durch positive und negative Zahlen unterschieden. 3. Einkommen und Ausgaben befinden sich in derselben Spalte, wobei ein Transaktionstypfeld sie unterscheidet. 4. Sonderbehandlung für spezielle Banken. Das heißt, wir müssen den entsprechenden Verarbeitungslogikalgorithmus basierend auf dem entsprechenden Einweis der Analyse Zu diesem Zeitpunkt können wir den Strategiemodus verwenden, um die Pipelines verschiedener Vorlagen mit verschiedenen Prozessoren zu verarbeiten und den entsprechenden Strategiealgorithmus gemäß der Vorlage zu finden. Selbst wenn wir in Zukunft einen anderen Typ hinzufügen, müssen wir nur einen neuen Prozessor hinzufügen, der einen hohen Zusammenhalt, eine niedrige Kopplung aufweist und skalierbar ist. Definieren Sie die Prozessorschnittstelle und verwenden Sie verschiedene Prozessoren, um die Verarbeitungslogik zu implementieren. Geben Sie alle Prozessoren in öffentliche Schnittstelle DataProzessor { /** * Verarbeitungsflussdaten * @param bankflowtemPlatedo Flow Indexdaten * @param Zeile * @zurückkehren */ BanktransactionFlowdo doprocess (bankflowtemplatedo bankflowtemplatedo, list <string> row); /** * Ob die Vorlage verarbeitet werden kann */ boolean issupport (bankflowtemplatedo bankflowtemplatedo); } // Prozessorkontext @Service @Slf4j öffentliche Klasse Bankflowdatacontext { // Injizieren Sie alle Prozessoren in die Karte @autowired private Liste <Dataprocessor> Prozessoren; // Ermitteln Sie den entsprechenden Prozessor, um die Pipeline Public void Process () {zu verarbeiten DataProcessor processor = getProcessor (bankflowtemplatedo); für (DataProcessor -Prozessor: Prozessoren) { if (processor.issupport (bankflowtemplatedo)) { // Zeile ist eine Reihe von Flussdatenprozessor.doprocess (Bankflowtemplatedo, Row); brechen; } } } } Definieren Sie den Standardprozessor, um normale Vorlagen zu /** *Standardprozessor: Vorlage der Standard -Pipeline -Vorlage* */ @Component ("defaultDataprocessor") @Slf4j Public Class DefaultDataprocessor implementiert Dataprocessor { @Überschreiben public banktransactionflowdo doprocess (bankflowtemplatedo bankflowtemplatedo) { // Die Verarbeitungslogik -Details weglassen. RECHTE BANKTRANSACTEFLOWDO; } @Überschreiben öffentliche String -Strategie (Bankflowtemplatedo bankflowtemplatedo) { // lasse das Urteil aus, ob die Pipeline boolean isdefault = true analysiert werden kann; ISDEFAULT zurückgeben; } } Durch das Strategiemuster weisen wir verschiedenen Verarbeitungsklassen unterschiedliche Verarbeitungslogiken zu, die vollständig entkoppelt und leicht zu erweitern sind. Verwenden Sie die eingebettete Tomcat-Methode, um den Quellcode zu debuggen: GitHub: https://github.com/uniquedong/tomcat-embedded Das oben genannte Inhalt der Analyse von Tomcat -Architekturprinzipien auf architektonisches Design. Das könnte Sie auch interessieren:
|
<<: CSS zur Erzielung einer kompatiblen Textausrichtung in verschiedenen Browsern
>>: HTML-Code für feste Titelspalte und spezifische Implementierungscode für die Titelkopftabelle
Drop-Shadow und Box-Shadow sind beide CSS-Eigensc...
Um das zuletzt erwähnte Problem zu lösen, habe ic...
Ich verwende Redis seit Kurzem und finde es recht...
Inhaltsverzeichnis Benutzerverwaltung Neuen Benut...
Was ist eine HTML-Datei? HTML steht für Hyper Text...
Vuex ist ein speziell für Vue.js-Anwendungen entw...
Vorwort Normalerweise wird für MySQL-Abfragen mit...
Inhaltsverzeichnis brauchen Kernidee Zwei Möglich...
Installieren Sie MySQL unter Win10 1. Laden Sie M...
Vereinfacht ausgedrückt geht es beim Erstellen ein...
Inhaltsverzeichnis 502 Bad Gateway Fehlerbildung ...
BEM ist ein komponentenbasierter Ansatz zur Weben...
Shopify Plus ist die Enterprise-Version der von u...
In MySQL gibt es überall Caches. Wenn ich den Que...
Exportieren einer einzelnen Tabelle mysqldump -u ...