Implementierung der Spring Framework-API von Grund auf neu. Komplettlösung für Anfänger. Teil 1



Spring Framework ist eines der kompliziertesten Frameworks zum Verstehen und Lernen. Die meisten Entwickler lernen es langsam, durch praktische Aufgaben und Google. Dieser Ansatz ist nicht effektiv, da er kein vollständiges Bild liefert und gleichzeitig teuer ist.

Ich möchte Ihnen einen grundlegend neuen Ansatz für das Studium des Frühlings anbieten. Es besteht in der Tatsache, dass eine Person eine Reihe von speziell vorbereiteten Tutorials durchläuft und die Funktion des Frühlings unabhängig implementiert. Die Besonderheit dieses Ansatzes besteht darin, dass er nicht nur die untersuchten Aspekte von Spring zu 100% versteht, sondern auch den Java-Kern (Anmerkungen, Reflexion, Dateien, Generika) erheblich steigert.

Der Artikel gibt Ihnen ein unvergessliches Erlebnis und Sie fühlen sich wie ein Pivotal-Entwickler. Schritt für Schritt werden Sie Ihre Klassen zu Bohnen machen und ihren Lebenszyklus organisieren (wie in einem echten Frühling). Die Klassen, die Sie implementieren, sind BeanFactory , Component , Service , BeanPostProcessor , BeanNameAware , BeanFactoryAware , InitializingBean , PostConstruct , PreDestroy , DisposableBean , ApplicationContext , ApplicationListener , ContextClosedEvent .

Ein bisschen über mich


Mein Name ist Jaroslawisch und ich bin ein Java-Entwickler mit 4 Jahren Erfahrung. Im Moment arbeite ich für EPAM Systems (SPB) und beschäftige mich intensiv mit den Technologien, die wir verwenden. Sehr oft muss ich mich mit dem Frühling auseinandersetzen, und ich sehe darin einen Mittelweg, auf dem man wachsen kann (jeder kennt Java gut, aber zu spezifische Tools und Technologien können kommen und gehen).

Vor ein paar Monaten habe ich die Spring Professional v5.0-Zertifizierung bestanden (ohne Kurse zu belegen). Danach dachte ich darüber nach, wie ich anderen Menschen das Springen beibringen könnte. Leider gibt es derzeit keine effektive Lehrmethode. Die meisten Entwickler haben eine sehr oberflächliche Vorstellung vom Framework und seinen Funktionen. Das Debuggen der Quellquellen ist aus Sicht des Trainings zu schwierig und absolut nicht effektiv (das hat mir irgendwie gefallen). 10 Projekte machen? Ja, irgendwo können Sie Ihr Wissen vertiefen und viel praktische Erfahrung sammeln, aber vieles, was sich „unter der Haube“ befindet, wird sich niemals vor Ihnen öffnen. Frühling in Aktion lesen? Cool, aber teuer im Aufwand. Ich habe es zu 40% bearbeitet (während der Vorbereitung auf die Zertifizierung), aber es war nicht einfach.

Der einzige Weg, etwas bis zum Ende zu verstehen, besteht darin, es selbst zu entwickeln. Vor kurzem hatte ich die Idee, dass Sie eine Person durch ein interessantes Tutorial führen können, das die Entwicklung ihres DI-Frameworks überwacht. Das Hauptmerkmal wird sein, dass die API mit der untersuchten API übereinstimmt. Das Besondere an diesem Ansatz ist, dass eine Person zusätzlich zu einem tiefen (ohne Leerzeichen) Verständnis des Frühlings eine RIESIGE Erfahrung in Java Core erhält. Ehrlich gesagt habe ich selbst während der Vorbereitung des Artikels viele neue Dinge gelernt, sowohl über Spring als auch über Java Core. Beginnen wir mit der Entwicklung!

Projekt von Grund auf neu


Öffnen Sie zunächst Ihre Lieblings-IDE und erstellen Sie ein Projekt von Grund auf neu. Wir werden keine Maven-Bibliotheken oder Bibliotheken von Drittanbietern verbinden. Wir werden nicht einmal Spring-Abhängigkeiten verbinden. Unser Ziel ist es, eine API zu entwickeln, die der Spring-API am ähnlichsten ist, und sie selbst zu implementieren.

Erstellen Sie in einem sauberen Projekt zwei Hauptpakete. Das erste Paket ist Ihre Anwendung ( com.kciray ) und die darin enthaltene Main.java Klasse. Das zweite Paket ist org.springframework. Ja, wir werden die Paketstruktur der ursprünglichen Feder, den Namen ihrer Klassen und ihre Methoden duplizieren. Es gibt einen so interessanten Effekt: Wenn Sie etwas Eigenes erschaffen, erscheint das Eigene einfach und verständlich. Wenn Sie dann in großen Projekten arbeiten, scheint es Ihnen, dass alles dort basierend auf Ihrem Werkstück erstellt wird. Dieser Ansatz kann sich sehr positiv auf das Verständnis des gesamten Systems, dessen Verbesserung, Behebung von Fehlern, Lösen von Problemen usw. auswirken.

Wenn Sie Probleme haben, können Sie hier ein Arbeitsprojekt durchführen.

Erstellen Sie einen Container


Legen Sie zunächst die Aufgabe fest. Angenommen, wir haben zwei Klassen - ProductFacade und PromotionService . Stellen Sie sich nun vor, Sie möchten diese Klassen miteinander verbinden, damit die Klassen selbst nichts voneinander wissen (Muster DI). Wir benötigen eine separate Klasse, die alle diese Klassen verwaltet und die Abhängigkeiten zwischen ihnen ermittelt. Nennen wir es einen Container. Lassen Sie uns die Container Klasse erstellen ... Obwohl nein, warten Sie! Spring hat keine einzige Containerklasse. Wir haben viele Container-Implementierungen, und alle diese Implementierungen können in zwei Typen unterteilt werden - Bin-Fabriken und Kontexte. Die Bin-Factory erstellt Beans und verknüpft sie miteinander (Dependency Injection, DI). Der Kontext funktioniert ähnlich und fügt einige zusätzliche Funktionen hinzu (z. B. Internationalisierung von Nachrichten). Da wir diese zusätzlichen Funktionen jetzt jedoch nicht benötigen, werden wir mit der Behälterfabrik zusammenarbeiten.

Erstellen Sie eine neue BeanFactory Klasse und BeanFactory sie in das Paket org.springframework.beans.factory . Lassen Sie Map<String, Object> singletons in dieser Klasse gespeichert werden, in der die id Bin dem Bin selbst zugeordnet ist. Fügen Sie die Object getBean(String beanName) -Methode hinzu, mit der die Beans nach Bezeichner Object getBean(String beanName) werden.

 public class BeanFactory { private Map<String, Object> singletons = new HashMap(); public Object getBean(String beanName){ return singletons.get(beanName); } } 

Bitte beachten Sie, dass BeanFactory und FactoryBean zwei verschiedene Dinge sind. Die erste ist die Behälterfabrik (Container), und die zweite ist die Behälterfabrik, die sich im Behälter befindet und auch Behälter produziert. Fabrik in der Fabrik. Wenn Sie zwischen diesen Definitionen verwechselt werden, können Sie sich daran erinnern, dass im Englischen das zweite Substantiv das führende ist und das erste so etwas wie ein Adjektiv. In Bean Factory ist das Hauptwort die Fabrik und in Factory Bean die Bohne.

Erstellen Sie ProductService Klassen ProductService und PromotionsService . ProductService gibt das Produkt aus der Datenbank zurück. ProductService müssen Sie jedoch prüfen, ob für dieses Produkt Rabatte (Sonderangebote) gelten. Im E-Commerce wird reduzierte Arbeit häufig einer separaten Serviceklasse (und manchmal einem Webdienst eines Drittanbieters) zugeordnet.

 public class PromotionsService { } public class ProductService { private PromotionsService promotionsService; public PromotionsService getPromotionsService() { return promotionsService; } public void setPromotionsService(PromotionsService promotionsService) { this.promotionsService = promotionsService; } } 

Jetzt müssen wir unseren Container ( BeanFactory ) dazu bringen, unsere Klassen zu erkennen, sie für uns zu erstellen und ineinander zu injizieren. Vorgänge wie new ProductService() sollten sich im Container befinden und für den Entwickler ausgeführt werden. Verwenden wir den modernsten Ansatz (Klassenscannen und Anmerkungen). Dazu müssen wir mit den @Component eine @Component Annotation erstellen ( org.springframework.beans.factory.stereotype ).

 @Retention(RetentionPolicy.RUNTIME) public @interface Component { } 

Standardmäßig werden Anmerkungen nicht in den Speicher geladen, während das Programm ausgeführt wird ( RetentionPolicy.CLASS ). Wir haben dieses Verhalten durch eine neue Aufbewahrungsrichtlinie ( RetentionPolicy.RUNTIME ) geändert.

@Component nun @Component vor den ProductService Klassen und vor dem PromotionService .

 @Component public class ProductService { //... } @Component public class PromotionService { //... } 


Wir benötigen BeanFactory um unser Paket ( com.kciray ) BeanFactory scannen und darin Klassen zu finden, die von @Component Anmerkungen @Component . Diese Aufgabe ist alles andere als trivial. In Java Core gibt es keine vorgefertigte Lösung , und wir müssen selbst eine Krücke bauen. Tausende Federanwendungen verwenden das Scannen von Komponenten durch diese Krücke. Du hast die schreckliche Wahrheit erfahren. Sie müssen die ClassLoader aus ClassLoader und prüfen, ClassLoader sie mit ".class" enden oder nicht. Anschließend müssen Sie ihren vollständigen Namen erstellen und Klassenobjekte daraus ziehen!

Ich möchte Sie sofort warnen, dass es viele geprüfte Ausnahmen geben wird. Seien Sie also bereit, diese zu verpacken. Aber zuerst entscheiden wir, was wir wollen. Wir möchten BeanFactory eine spezielle Methode BeanFactory und sie in Main aufrufen:

 //BeanFactory.java public class BeanFactory{ public void instantiate(String basePackage) { } } //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); 

Als nächstes müssen wir ClassLoader bekommen. Es ist für das Laden von Klassen verantwortlich und wird ganz einfach extrahiert:

 ClassLoader classLoader = ClassLoader.getSystemClassLoader(); 

Sie haben wahrscheinlich bereits bemerkt, dass die Pakete durch einen Punkt und die Dateien durch einen Schrägstrich getrennt sind. Wir müssen den Stapelpfad in den Ordnerpfad konvertieren und so etwas wie List<URL> (die Pfade in Ihrem Dateisystem, in denen Sie nach Klassendateien suchen können).

 String path = basePackage.replace('.', '/'); //"com.kciray" -> "com/kciray" Enumeration<URL> resources = classLoader.getResources(path); 

Also warte einen Moment! Enumeration<URL> ist keine List<URL> . Worum geht es hier? Oh, Horror, dies ist der alte Vorläufer von Iterator , der seit Java 1.0 verfügbar ist. Dies ist das Erbe, mit dem wir uns befassen müssen. Wenn es möglich ist, Iterable mit for zu durchlaufen (alle Sammlungen implementieren es), müssen Sie im Fall von Enumeration einen Handle-Bypass durch while(resources.hasMoreElements()) und nextElement() . Es gibt jedoch keine Möglichkeit, Elemente aus der Sammlung zu entfernen. Nur 1996, nur Hardcore. Oh ja, in Java 9 haben sie die Enumeration.asIterator() -Methode hinzugefügt, damit Sie sie Enumeration.asIterator() können.

Gehen wir weiter. Wir müssen die Ordner extrahieren und den Inhalt jedes einzelnen von ihnen durcharbeiten. Konvertieren Sie die URL in eine Datei und erhalten Sie den Namen. Hierbei ist zu beachten, dass verschachtelte Pakete nicht gescannt werden, um den Code nicht zu komplizieren. Sie können Ihre Aufgabe komplizieren und eine Rekursion durchführen, wenn Sie dies wünschen.

 while (resources.hasMoreElements()) { URL resource = resources.nextElement(); File file = new File(resource.toURI()); for(File classFile : file.listFiles()){ String fileName = classFile.getName();//ProductService.class } } 

Als nächstes müssen wir den Dateinamen ohne die Erweiterung erhalten. Auf dem Hof ​​im Jahr 2018 hat Java seit vielen Jahren File I / O (NIO 2) entwickelt, kann die Erweiterung jedoch immer noch nicht vom Dateinamen trennen. Ich muss mein eigenes Fahrrad bauen, weil Wir haben uns entschieden, keine Bibliotheken von Drittanbietern wie Apache Commons zu verwenden. Verwenden wir den alten Großvater-Weg lastIndexOf(".") :

 if(fileName.endsWith(".class")){ String className = fileName.substring(0, fileName.lastIndexOf(".")); } 

Als nächstes können wir das Klassenobjekt unter Verwendung des vollständigen Klassennamens erhalten (dazu nennen wir die Klasse der Klassenklasse):

 Class classObject = Class.forName(basePackage + "." + className); 

Okay, jetzt sind unsere Klassen in unseren Händen. Außerdem müssen nur diejenigen hervorgehoben werden, die die Annotation @Component :

 if(classObject.isAnnotationPresent(Component.class)){ System.out.println("Component: " + classObject); } 

Ausführen und überprüfen. Die Konsole sollte ungefähr so ​​aussehen:

 Component: class com.kciray.ProductService Component: class com.kciray.PromotionsService 

Jetzt müssen wir unsere Bohne erstellen. Sie müssen so etwas wie new ProductService() , aber für jede Bean haben wir unsere eigene Klasse. Die Reflexion in Java bietet uns eine universelle Lösung (der Standardkonstruktor heißt):

 Object instance = classObject.newInstance();//=new CustomClass() 

Als nächstes müssen wir diese Bean in Map<String, Object> singletons . Wählen Sie dazu den Bean-Namen (seine ID). In Java rufen wir Variablen wie Klassen auf (nur der erste Buchstabe ist klein geschrieben). Dieser Ansatz kann auch auf Beans angewendet werden, da Spring ein Java-Framework ist! Konvertieren Sie den Bin-Namen so, dass der erste Buchstabe klein ist, und fügen Sie ihn der Karte hinzu:

 String beanName = className.substring(0, 1).toLowerCase() + className.substring(1); singletons.put(beanName, instance); 

Stellen Sie jetzt sicher, dass alles funktioniert. Der Container muss Beans erstellen und diese müssen namentlich abgerufen werden. Bitte beachten Sie, dass der Name Ihrer instantiate() Methode und der Name der classObject.newInstance(); -Methode classObject.newInstance(); habe eine gemeinsame Wurzel. Darüber hinaus ist instantiate() Teil des Lebenszyklus der Bohne. In Java ist alles miteinander verbunden!

 //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); ProductService productService = (ProductService) beanFactory.getBean("productService"); System.out.println(productService);//ProductService@612 


Versuchen Sie auch, die Annotation org.springframework.beans.factory.stereotype.Service implementieren. Es hat genau die gleiche Funktion wie @Component , wird jedoch anders aufgerufen. Der springende Punkt liegt im Namen - Sie zeigen, dass die Klasse ein Dienst ist, nicht nur eine Komponente. Dies ist so etwas wie konzeptionelles Tippen. In der Frühjahrszertifizierung gab es die Frage "Welche Anmerkungen sind stereotyp?" (von den aufgelisteten). ” Stereotype Annotationen sind also diejenigen, die im stereotype Paket enthalten sind.

Füllen Sie die Eigenschaften


Schauen Sie sich das Diagramm unten an, es zeigt den Beginn des Lebenszyklus der Bohne. Was wir vorher gemacht haben, ist Instantiate (Erstellen von Beans durch newInstance() ). Der nächste Schritt ist die Kreuzinjektion von Bohnen (Abhängigkeitsinjektion, es ist auch die Inversion der Kontrolle (IoC)). Sie müssen die Eigenschaften der Bohnen durchgehen und verstehen, welche Eigenschaften Sie injizieren müssen. Wenn Sie productService.getPromotionsService() aufrufen, erhalten Sie null , weil Abhängigkeit noch nicht hinzugefügt.



Erstellen Sie zunächst das Paket org.springframework.beans.factory.annotation und fügen Sie die Annotation @Autowired . Die Idee ist, Felder, die Abhängigkeiten sind, mit dieser Anmerkung zu kennzeichnen.

 @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { } 

Fügen Sie es als Nächstes der Eigenschaft hinzu:

 @Component public class ProductService { @Autowired PromotionsService promotionsService; //... } 

Jetzt müssen wir unserer BeanFactory beibringen, diese Anmerkungen BeanFactory finden und Abhängigkeiten von ihnen BeanFactory . Fügen Sie hierfür eine separate Methode hinzu und rufen Sie sie von Main :

 public class BeanFactory { //... public void populateProperties(){ System.out.println("==populateProperties=="); } } 

Als Nächstes müssen wir nur alle unsere Beans in der singletons Map durchgehen und für jede Bean alle ihre Felder object.getClass().getDeclaredFields() Methode object.getClass().getDeclaredFields() gibt alle Felder zurück, einschließlich privater Felder). Überprüfen Sie, ob das Feld eine @Autowired Anmerkung enthält:

 for (Object object : singletons.values()) { for (Field field : object.getClass().getDeclaredFields()) { if (field.isAnnotationPresent(Autowired.class)) { } } } 

Als nächstes müssen wir noch einmal alle Behälter durchgehen und ihren Typ sehen - plötzlich ist dies der Typ, den unser Behälter für sich nehmen möchte. Ja, wir bekommen einen dreidimensionalen Zyklus!

 for (Object dependency : singletons.values()) { if (dependency.getClass().equals(field.getType())) { } } 

Wenn wir die Sucht gefunden haben, müssen wir sie injizieren. Das erste, woran Sie denken könnten, ist, das Feld " promotionsService direkt mit Reflektion zu schreiben. Aber so funktioniert der Frühling nicht. Wenn das Feld einen private Modifikator hat, müssen wir ihn zuerst als public festlegen, dann unseren Wert schreiben und dann wieder auf private (um die Integrität aufrechtzuerhalten). Klingt nach einer großen Krücke. Anstelle einer großen Krücke machen wir eine kleine Krücke (wir bilden den Namen des Setzers und nennen ihn):

 String setterName = "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);//setPromotionsService System.out.println("Setter name = " + setterName); Method setter = object.getClass().getMethod(setterName, dependency.getClass()); setter.invoke(object, dependency); 

Führen Sie nun Ihr Projekt aus und stellen Sie sicher, dass beim Aufrufen von productService.getPromotionsService() anstelle von null unsere Bean zurückgegeben wird.

Was wir implementiert haben, ist die Injektion nach Typ. Es gibt auch eine Injektion nach Namen ( javax.annotation.Resource annotation). Es unterscheidet sich darin, dass anstelle des Feldtyps sein Name extrahiert wird und dementsprechend die Abhängigkeit von der Karte. Hier ist alles ähnlich, auch in etwas Einfacherem. Ich empfehle, dass Sie experimentieren und Ihre eigene Bean erstellen, diese dann mit @Resource injizieren und die Methode populateProperties() .

Wir unterstützen Bohnen, die über ihren Namen Bescheid wissen




Es gibt Zeiten, in denen Sie seinen Namen in den Papierkorb bekommen müssen. Ein solches Bedürfnis entsteht nicht oft, weil Behälter sollten im Wesentlichen nicht voneinander wissen und dass sie Behälter sind. In den ersten Versionen des Frühlings wurde angenommen, dass die Bean ein POJO (Plain Old Java Objec, das gute alte Java-Objekt) ist und die gesamte Konfiguration in XML-Dateien gerendert und von der Implementierung getrennt wird. Wir implementieren diese Funktionalität jedoch, da die Namensinjektion Teil des Lebenszyklus des Behälters ist.

Woher wissen wir, welche Bohne wissen will, wie er heißt und was er nicht will? Das erste, was mir in den Sinn kommt, ist, eine neue Annotation vom Typ @InjectName und sie in Felder vom Typ String zu formen. Diese Lösung ist jedoch zu allgemein und ermöglicht es Ihnen, sich viele Male in den Fuß zu schießen (platzieren Sie diese Anmerkung auf Feldern unangemessenen Typs (nicht auf Zeichenfolge) oder versuchen Sie, einen Namen in mehrere Felder derselben Klasse einzufügen). Es gibt eine andere Lösung, genauer - eine spezielle Schnittstelle mit einer Setter-Methode zu erstellen. Alle Bins, die es implementieren, erhalten ihren Namen. Erstellen Sie die BeanNameAware Klasse im Paket org.springframework.beans.factory :

 public interface BeanNameAware { void setBeanName(String name); } 

Lassen Sie es als nächstes von unserem PromotionsService implementieren:

 @Component public class PromotionsService implements BeanNameAware { private String beanName; @Override public void setBeanName(String name) { beanName = name; } public String getBeanName() { return beanName; } } 

Und schließlich fügen Sie der Bohnenfabrik eine neue Methode hinzu. Hier ist alles einfach - wir gehen unseren Bin-Singleton durch, prüfen, ob der Bin unsere Schnittstelle implementiert, und rufen den Setter auf:

 public void injectBeanNames(){ for (String name : singletons.keySet()) { Object bean = singletons.get(name); if(bean instanceof BeanNameAware){ ((BeanNameAware) bean).setBeanName(name); } } } 

Führen Sie aus und stellen Sie sicher, dass alles funktioniert:

 BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); beanFactory.populateProperties(); beanFactory.injectBeanNames(); //... System.out.println("Bean name = " + promotionsService.getBeanName()); 

Es ist zu beachten, dass es im Frühjahr andere ähnliche Schnittstellen gibt. Ich empfehle, dass Sie die BeanFactoryAware- Schnittstelle selbst implementieren, damit Beans einen Link zur Bean Factory erhalten. Es wird auf ähnliche Weise implementiert.

Bohnen initialisieren




Stellen Sie sich vor, Sie müssen nach dem Einfügen der Abhängigkeiten Code ausführen (bin-Eigenschaften sind festgelegt). In einfachen Worten, wir müssen dem Bin die Möglichkeit geben, sich selbst zu initialisieren. Alternativ können wir eine InitializingBean Schnittstelle erstellen und die Signatur der void afterPropertiesSet() -Methode darin void afterPropertiesSet() . Die Implementierung dieses Mechanismus ist genau die gleiche wie für die BeanNameAware Schnittstelle, sodass sich die Lösung unter dem Spoiler befindet. Übe und mache es in einer Minute selbst:

Bean-Initialisierungslösung
 //InitializingBean.java package org.springframework.beans.factory; public interface InitializingBean { void afterPropertiesSet(); } //BeanFactory.java public void initializeBeans(){ for (Object bean : singletons.values()) { if(bean instanceof InitializingBean){ ((InitializingBean) bean).afterPropertiesSet(); } } } //Main.java beanFactory.initializeBeans(); 



Hinzufügen von Postprozessoren


Stellen Sie sich an die Stelle der ersten Frühlingsentwickler. Ihr Framework wächst und ist bei Entwicklern sehr beliebt. Täglich werden Briefe an die E-Mail gesendet, in denen Sie aufgefordert werden, die eine oder andere nützliche Funktion hinzuzufügen. Wenn Sie für jede dieser Funktionen eine eigene Schnittstelle hinzufügen und diese im Lebenszyklus der Bean überprüfen, wird sie (der Lebenszyklus) mit unnötigen Informationen verstopft. Stattdessen können wir eine universelle Schnittstelle erstellen, über die Sie eine Logik hinzufügen können (absolut jede, unabhängig davon, ob nach Anmerkungen gesucht wird, der Behälter durch einen anderen Behälter ersetzt wird, einige spezielle Eigenschaften festgelegt werden usw.).

Lassen Sie uns darüber nachdenken, wofür diese Schnittstelle gedacht ist. Es muss eine Nachbearbeitung der Beans durchgeführt werden, daher kann es BeanPostProcessor genannt werden. Wir stehen jedoch vor einer schwierigen Frage: Wann sollte die Logik befolgt werden? Schließlich können wir es vor der Initialisierung ausführen, aber wir können es danach ausführen. Für einige Aufgaben ist die erste Option besser, für andere - die zweite ... Wie soll man sein?

Wir können beide Optionen gleichzeitig aktivieren. Lassen Sie einen Postprozessor zwei Logiken, zwei Methoden tragen. Eine wird vor der Initialisierung (vor der afterPropertiesSet() -Methode) und die andere nach der Initialisierung ausgeführt. Lassen Sie uns nun über die Methoden selbst nachdenken - welche Parameter sollten sie haben? Offensichtlich muss die Object bean selbst ( Object bean ) vorhanden sein. Der Einfachheit halber können Sie zusätzlich zur Bohne den Namen dieser Bohne übergeben. Sie erinnern sich, dass der Behälter selbst seinen Namen nicht kennt. Und wir möchten nicht alle Beans zwingen, die BeanNameAware-Schnittstelle zu implementieren. Auf Postprozessorebene kann der Bean-Name jedoch sehr nützlich sein. Daher fügen wir es als zweiten Parameter hinzu.

Und was sollte die Methode bei der Nachbearbeitung der Bean zurückgeben? Lassen Sie es den Papierkorb selbst zurückgeben. Dies gibt uns Superflexibilität, da Sie anstelle eines Bin ein Proxy-Objekt verschieben können, das seine Aufrufe umschließt (und die Sicherheit erhöht). Oder Sie können ein anderes Objekt vollständig zurückgeben, indem Sie den Bin erneut erstellen. Entwicklern wird eine sehr große Handlungsfreiheit eingeräumt. Unten finden Sie die endgültige Version der gestalteten Benutzeroberfläche:

 package org.springframework.beans.factory.config; public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object bean, String beanName); Object postProcessAfterInitialization(Object bean, String beanName); } 

Als nächstes müssen wir unserer Bohnenfabrik eine Liste einfacher Prozessoren hinzufügen und neue hinzufügen. Ja, dies ist eine reguläre ArrayList.

 //BeanFactory.java private List<BeanPostProcessor> postProcessors = new ArrayList<>(); public void addPostProcessor(BeanPostProcessor postProcessor){ postProcessors.add(postProcessor); } 

Ändern Sie nun die Methode initializeBeans so, dass Postprozessoren berücksichtigt werden:

 public void initializeBeans() { for (String name : singletons.keySet()) { Object bean = singletons.get(name); for (BeanPostProcessor postProcessor : postProcessors) { postProcessor.postProcessBeforeInitialization(bean, name); } if (bean instanceof InitializingBean) { ((InitializingBean) bean).afterPropertiesSet(); } for (BeanPostProcessor postProcessor : postProcessors) { postProcessor.postProcessAfterInitialization(bean, name); } } } 

Erstellen wir einen kleinen Postprozessor, der Anrufe einfach an die Konsole zurückverfolgt und zu unserer Bean Factory hinzufügt:

 public class CustomPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { System.out.println("---CustomPostProcessor Before " + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) { System.out.println("---CustomPostProcessor After " + beanName); return bean; } } 

 //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.addPostProcessor(new CustomPostProcessor()); 


Führen Sie nun aus und stellen Sie sicher, dass alles funktioniert. Erstellen Sie als Schulungsaufgabe einen @PostConstruct (javax.annotation.PostConstruct) Annotation @PostConstruct (javax.annotation.PostConstruct) . Es bietet eine alternative Möglichkeit zum Initialisieren (verwurzelt in Java, nicht im Frühjahr). Das Wesentliche ist, dass Sie die Annotation auf eine Methode setzen. Diese Methode wird VOR der Standard-Federinitialisierung (InitializingBean) aufgerufen.

Stellen Sie sicher, dass Sie alle Anmerkungen und Pakete (auch javax.annotation) manuell erstellen. Verbinden Sie die Abhängigkeiten nicht! Auf diese Weise können Sie den Unterschied zwischen dem Federkern und seinen Verlängerungen (Javax-Unterstützung) erkennen und sich daran erinnern. Dies wird auch in Zukunft einen Stil beibehalten.

Sie werden daran interessiert sein, dass in einem echten Frühjahr die Annotation @PostConstructauf diese Weise über den Postprozessor CommonAnnotationBeanPostProcessor implementiert wird. Aber schauen Sie nicht dorthin, schreiben Sie Ihre Implementierung.

Zuletzt empfehle ich Ihnen void close(), der Klasse eine Methode hinzuzufügen BeanFactoryund zwei weitere Mechanismen auszuarbeiten. Die erste ist eine Anmerkung @PreDestroy (javax.annotation.PreDestroy), die für Methoden gedacht ist, die aufgerufen werden sollen, wenn der Container geschlossen wird. Die zweite ist die Schnittstelle org.springframework.beans.factory.DisposableBean, die die Methode enthält void destroy(). Alle Bins, die diese Schnittstelle ausführen, können sich selbst zerstören (z. B. Ressourcen freisetzen).

@PreDestroy + DisposableBean
 //DisposableBean.java package org.springframework.beans.factory; public interface DisposableBean { void destroy(); } //PreDestroy.java package javax.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface PreDestroy { } //DisposableBean.java public void close() { for (Object bean : singletons.values()) { for (Method method : bean.getClass().getMethods()) { if (method.isAnnotationPresent(PreDestroy.class)) { try { method.invoke(bean); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } if (bean instanceof DisposableBean) { ((DisposableBean) bean).destroy(); } } } 



Voller Bohnen-Lebenszyklus


Deshalb haben wir den gesamten Lebenszyklus des Behälters in seiner modernen Form implementiert. Ich hoffe, dieser Ansatz hilft Ihnen, sich daran zu erinnern.

Unser Lieblingskontext


Programmierer verwenden sehr oft den Begriff Kontext, aber nicht jeder versteht, was er wirklich bedeutet. Jetzt werden wir alles in Ordnung bringen. Wie ich am Anfang des Artikels bemerkt habe, ist der Kontext auch die Implementierung des Containers BeanFactory. Zusätzlich zu den Grundfunktionen (DI) werden noch einige coole Funktionen hinzugefügt. Eine dieser Funktionen ist das Senden und Verarbeiten von Ereignissen zwischen Bins.

Der Artikel erwies sich als zu groß und der Inhalt wurde abgeschnitten, sodass ich die Kontextinformationen unter den Spoiler stellte.

Wir erkennen den Kontext
. org.springframework.context , ApplicationContext . BeanFactory . , close() .

 public class ApplicationContext { private BeanFactory beanFactory = new BeanFactory(); public ApplicationContext(String basePackage) throws ReflectiveOperationException{ System.out.println("******Context is under construction******"); beanFactory.instantiate(basePackage); beanFactory.populateProperties(); beanFactory.injectBeanNames(); beanFactory.initializeBeans(); } public void close(){ beanFactory.close(); } } 


Main , , :

 ApplicationContext applicationContext = new ApplicationContext("com.kciray"); applicationContext.close(); 

, . close() , « » - . , :

 package org.springframework.context.event; public class ContextClosedEvent { } 

ApplicationListener , . , ( ApplicationListener<E> ). , Java-, . , , :

 package org.springframework.context; public interface ApplicationListener<E>{ void onApplicationEvent(E event); } 

ApplicationContext . close() , , . ApplicationListener<ContextClosedEvent> , onApplicationEvent(ContextClosedEvent) . , ?

 public void close(){ beanFactory.close(); for(Object bean : beanFactory.getSingletons().values()) { if (bean instanceof ApplicationListener) { } } } 

Aber nein. . bean instanceof ApplicationListener<ContextClosedEvent> . Java. (type erasure) , <T> <Object>. , ? , ApplicationListener<ContextClosedEvent> , ?

, , . , , , , :

 for (Type type: bean.getClass().getGenericInterfaces()){ if(type instanceof ParameterizedType){ ParameterizedType parameterizedType = (ParameterizedType) type; } } 

, , , — . , :

 Type firstParameter = parameterizedType.getActualTypeArguments()[0]; if(firstParameter.equals(ContextClosedEvent.class)){ Method method = bean.getClass().getMethod("onApplicationEvent", ContextClosedEvent.class); method.invoke(bean, new ContextClosedEvent()); } 

ApplicationListener:

 @Service public class PromotionsService implements BeanNameAware, ApplicationListener<ContextClosedEvent> { //... @Override public void onApplicationEvent(ContextClosedEvent event) { System.out.println(">> ContextClosed EVENT"); } } 

, Main , , :

 //Main.java void testContext() throws ReflectiveOperationException{ ApplicationContext applicationContext = new ApplicationContext("com.kciray"); applicationContext.close(); } 


Fazit


Anfangs plante ich diesen Artikel für Baeldung auf Englisch, aber dann dachte ich, dass das Publikum des Habré diesen Trainingsansatz positiv bewerten könnte. Wenn Ihnen meine Ideen gefallen haben, sollten Sie den Artikel unbedingt unterstützen. Wenn sie eine Bewertung von mehr als 30 erhält, verspreche ich, fortzufahren. Beim Schreiben des Artikels habe ich versucht, genau das Wissen über Spring Core zu zeigen, das am häufigsten verwendet wird und auch auf dem Core Spring 5.0 Certification Study Guide basiert . Mit Hilfe solcher Tutorials können Sie in Zukunft die gesamte Zertifizierung abdecken und den Frühling für Java-Entwickler zugänglicher machen.

Update 05/10/2018


Briefe kommen ständig mit Fragen zu mir "und wenn die Fortsetzung, warten wir auf ihn." Aber es gibt überhaupt keine Zeit und andere persönliche Projekte haben Priorität. Wenn einem von Ihnen die Idee jedoch wirklich gefallen hat, können Sie den engen Abschnitt des Frühlings studieren und einen Folgeartikel schreiben. Wenn Sie kein habr-Konto haben, kann ich einen Artikel aus meinem Konto veröffentlichen oder Ihnen helfen, eine Einladung zu erhalten.

Themenverteilung:
Spring Container - [Benutzername]
Spring AOP - [Benutzername]
Spring Web - [Benutzername]
Spring Cloud - [Benutzername]

Source: https://habr.com/ru/post/de419679/


All Articles