Welche Möglichkeiten bietet Spring, um sein Verhalten anzupassen?

Hallo alle zusammen. In Kontakt Vladislav Rodin. Derzeit bin ich Leiter des High Load Architect-Kurses bei OTUS und unterrichte auch Kurse zur Softwarearchitektur.

Neben dem Unterrichten schreibe ich auch urheberrechtlich geschütztes Material für den OTUS-Blog über den Haber und möchte mit dem heutigen Artikel zusammenfallen, um den Kurs "Entwickler auf dem Spring Framework" zu starten , der jetzt für die Rekrutierung offen ist.




Einführung


Aus Sicht des Lesers sieht der Code der Anwendung, die Spring verwendet, recht einfach aus: Einige Beans werden deklariert, Klassen werden mit Anmerkungen markiert, und dann werden die Beans bei Bedarf injiziert, alles funktioniert einwandfrei. Ein neugieriger Leser hat jedoch eine Frage: „Wie funktioniert es? Was ist los?". In diesem Artikel werden wir versuchen, diese Frage zu beantworten, aber nicht, um die müßige Neugier zu befriedigen.

Spring Framework ist dafür bekannt, flexibel genug zu sein und bietet Optionen zum Anpassen des Verhaltens des Frameworks. Im Frühling gibt es auch eine Reihe interessanter Regeln für das Anwenden bestimmter Anmerkungen (z. B. Transactional). Um die Bedeutung dieser Regeln zu verstehen, sie ableiten zu können und um zu verstehen, was und wie Sie im Frühjahr konfigurieren können, müssen Sie verschiedene Funktionsprinzipien für das verstehen, was sich im Frühjahr unter der Haube befindet. Wie Sie wissen, befreit die Kenntnis mehrerer Prinzipien von der Kenntnis vieler Tatsachen. Ich schlage vor, dass Sie sich mit diesen Prinzipien vertraut machen, wenn Sie sie natürlich noch nicht kennen.

Konfigurationen lesen


Zu Beginn müssen Sie die Konfigurationen in Ihrer Anwendung analysieren. Da es verschiedene Arten von Konfigurationen gibt (XML-, Groovy-, Java-Konfigurationen, Konfiguration basierend auf Anmerkungen), werden verschiedene Methoden zum Lesen verwendet. Auf die eine oder andere Weise wird eine Karte der Form Map <String, BeanDefinition> gesammelt, in der die Namen der Beans ihren Bean-Definitionen zugewiesen werden. Objekte der BeanDefinition-Klasse sind Metainformationen zu Beans und enthalten die ID, den Namen, die Klasse, die Zerstörungs- und die Init-Methode der Bean.

Beispiele für Klassen, die an diesem Prozess beteiligt sind: GroovyBeanDefinitionReader, XmlBeanDefinitionReader, AnnotatedBeanDefinitionReader, Implementierung der BeanDefinitionReader- Schnittstelle .

Beandefinitionen einrichten


Wir haben also Beschreibungen von Bohnen, aber es gibt keine Bohnen selbst, sie wurden noch nicht erstellt. Vor dem Erstellen von Beans bietet Spring die Möglichkeit, die resultierenden BeanDefinitions anzupassen. Für diese Zwecke wird die BeanFactoryPostProcessor- Schnittstelle verwendet . Es sieht aus wie das:

public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

Der Methodenparameter dieser Schnittstelle ermöglicht die Verwendung einer eigenen getBeanDefinitionNames-Methode, um die Namen abzurufen, mit denen Sie BeanDefinitions aus der Map abrufen und bearbeiten können.

Warum könnte dies erforderlich sein? Angenommen, einige Beans benötigen Details, um eine Verbindung zu einem externen System wie einer Datenbank herzustellen. Wir möchten, dass die Beans bereits mit den Details erstellt werden, aber die Details selbst werden in der Eigenschaftendatei gespeichert. Wir können einen der Standard-BeanFactoryPostProcessors - PropertySourcesPlaceholderConfigurer anwenden, der die Eigenschaft name in der BeanDefinition durch den tatsächlichen Wert ersetzt, der in der Eigenschaftendatei gespeichert ist. Das heißt, es ersetzt tatsächlich Value ("user") durch Value ("root") in BeanDefinion. Damit dies funktioniert, muss natürlich der PropertySourcesPlaceholderConfigurer verbunden sein. Dies ist jedoch nicht darauf beschränkt. Sie können Ihren BeanFactoryPostProcessor registrieren, in dem Sie die Logik implementieren können, die Sie zur Verarbeitung von BeanDefinitions benötigen.

Bohnen erstellen


Zu diesem Zeitpunkt haben wir eine Map, die die Namen der Beans enthält, die sich in den Schlüsseln befinden, und die konfigurierten BeanDefinitions anhand der Werte. Jetzt müssen Sie diese Bohnen erstellen. Dies ist, was BeanFactory tut . Hier können Sie jedoch Anpassungen vornehmen, indem Sie Ihre FactoryBean schreiben und registrieren . FactoryBean ist eine Schnittstelle des Formulars:

public interface FactoryBean {
    T getObject() throws Exception;
    Class<?> getObjectType();
    boolean isSingleton();
}

Daher erstellt eine BeanFactory eine Bean selbst, wenn der Bean-Klasse keine FactoryBean entspricht, oder fordert die FactoryBean auf, diese Bean zu erstellen. Es gibt eine kleine Nuance: Wenn der Umfang der Bean Singletone ist, wird die Bean zu diesem Zeitpunkt erstellt. Wenn ein Prototyp vorhanden ist, wird diese Bean jedes Mal, wenn sie benötigt wird, von der BeanFactory angefordert.

Als Ergebnis erhalten wir wieder eine Karte, die jedoch bereits etwas anders ist: Die Schlüssel enthalten die Namen der Bohnen und die Werte der Bohnen selbst. Dies gilt jedoch nur für Singletones.

Bohnen konfigurieren


Jetzt kommt die interessanteste Etappe. Wir haben eine Karte mit den erstellten Beans, aber diese Beans wurden noch nicht konfiguriert. Das heißt, wir haben keine Anmerkungen verarbeitet, die den Status der Bean festlegen: Autowired, Value. Wir haben auch keine Anmerkungen verarbeitet, die das Verhalten der Bean ändern: Transactional, Async. Mit BeanPostProcessors , die wiederum Implementierungen der entsprechenden Schnittstelle sind, können wir dieses Problem lösen :

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

Wir sehen 2 Methoden mit beängstigenden, aber umfassenden Namen. Beide Methoden verwenden eine Bean als Eingabe, von der Sie abfragen können, um welche Klasse es sich handelt, und verwenden dann die Reflection-API, um Anmerkungen zu verarbeiten. Rückgabe-Bean-Methoden, möglicherweise ersetzt durch Proxy.

Für jede Bean, bevor sie in den Kontext gestellt wird, geschieht Folgendes: Die postProcessBeforeInitialization-Methoden werden für alle BeanPostProcessors ausgelöst, dann wird die init-Methode ausgelöst und anschließend werden die postProcessAfterInitialization-Methoden auch für alle BeanPostProcessors ausgelöst.

Diese beiden Methoden haben unterschiedliche Semantiken: postProcessBeforeInitialization verarbeitet Statusanmerkungen, postProcessAfterInitialization verarbeitet das Verhalten, da Proxy zum Verarbeiten des Verhaltens verwendet wird, was zum Verlust von Anmerkungen führen kann. Deshalb ändert sich das Verhalten an letzter Stelle.

Wo ist die Anpassung? Wir können unsere Annotation BeanPostProcessor dafür schreiben, und Spring wird sie verarbeiten. Damit der BeanPostProcessor funktioniert, muss er jedoch auch als Bean registriert sein.

Um beispielsweise eine Zufallszahl in das Feld einzubetten, erstellen wir eine InjectRandomInt-Annotation (hängen an den Feldern), erstellen und registrieren einen InjectRandomIntBeanPostProcessor, in dessen erster Methode wir die erstellte Annotation verarbeiten, und in der zweiten Methode geben wir einfach die eingehende Bean zurück.

Um die Beans zu profilieren, erstellen Sie eine Profilanmerkung, die an Methoden gesendet wird, erstellen und registrieren Sie einen ProfileBeanPostProcessor, bei der ersten Methode geben wir die eingehende Bean zurück, und bei der zweiten Methode geben wir einen Proxy zurück, der den Aufruf der ursprünglichen Methode mit Protokollierung der Beschneidungs- und Ausführungszeit umschließt.



Erfahren Sie mehr über den Kurs



All Articles