Hallo! Während die JavaRush-Administration an neuen Ebenen arbeitet, möchte ich eine Reihe von Schulungsartikeln zum Spring Framework starten. Ja, ich weiß, dass es im Internet bereits viel Material zu diesem Thema gibt, aber wie die Praxis zeigt, sind sie alle auf dem Niveau von Hello World. Ich möchte nicht darüber sprechen, wie man Anmerkungen richtig platziert, sondern darüber, wie das alles „unter der Haube“ funktioniert. Der Artikel richtet sich an diejenigen, die bereits auf die eine oder andere Weise mit diesem Framework gearbeitet haben und mit den Grundkonzepten vertraut sind.
Den Kontext initialisieren.
Beginnen wir also mit den Grundlagen. Einer der wichtigsten Punkte ist meiner Meinung nach, zu verstehen, wie der Kontext aufgebaut und Beans initialisiert werden. Wie Sie wissen, muss Spring konfiguriert werden, bevor es funktioniert. In vorsintflutlichen Zeiten geschah dies mithilfe von XML-Dateien (bei einigen Projekten, vor allem alten, wird dies bis heute getan). Hier ist ein kleines Beispiel einer solchen Konfigurationsdatei:<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="helloWorld" class="ru.javarush.HelloWorld">
<property name="message" value="Hello World!"/>
</bean>
</beans>
Im Großen und Ganzen reicht dies aus, um ein paar Controller zu erstellen und ein Startup zu starten (das nicht startet). Aber wie wird diese Konfiguration dafür sorgen, dass Spring funktioniert? Und hier wird es interessant. Damit unsere Konfiguration von Spring verstanden wird, gibt es eine XmlBeanDefinitionReader
. BeanDefinition
Dies ist eine interne Spring-Komponente, die XML scannt (analysiert) und basierend auf dem , was wir dort geschrieben haben, erstellt. BeanDefinition
ist ein Objekt, das Informationen über die Bean speichert. Dazu gehört: aus welcher Klasse es (die Bean) erstellt werden soll; Umfang; ob die verzögerte Initialisierung installiert ist; Ist es notwendig, vor dieser Bean weitere und andere Eigenschaften zu initialisieren, die in XML beschrieben sind? Alle empfangenen BeanDefinition
werden zu hinzugefügt HashMap
, wobei der Bezeichner der Name der Bean (von Ihnen festgelegt oder von Spring zugewiesen) und BeanDefinition
das Objekt selbst ist. Nachdem alles BeanDefinition
erschaffen ist, erscheint ein neuer Held auf der Bühne – BeanFactory
. Dieses Objekt durchläuft HashMap’e
s BeanDefinition
, erstellt darauf basierende Beans und legt sie in einem IoC-Container ab. Hier gibt es eine Nuance: Wenn die Anwendung gestartet wird, enthält der IoC-Container Beans mit einem Singleton-Bereich (standardmäßig festgelegt), während der Rest bei Bedarf erstellt wird (Prototyp, Anfrage, Sitzung). Und jetzt ein kleiner Exkurs: Lernen wir einen anderen Charakter kennen.
Lernen Sie BeanPostProcessor kennen. (BPP)
Tatsache ist, dass eine Bean nicht unbedingt eine Klasse von Geschäftslogik für Ihre Anwendung ist. Eine Bean wird auch Infrastruktur-Bean genannt. Kurz gesagt ist eine Infrastruktur-Bean eine Klasse, die Ihre Geschäftslogik-Beans konfiguriert (ja, zu viele Beans). Ich werde Ihnen weiter unten mehr darüber erzählen, aber um etwas klarer zu machen, was genau BPP konfiguriert, werde ich ein Beispiel geben. Kennt jeder die Zusammenfassung@Autowired
? Sie sind also AutowiredAnnotationBeanPostProcessor
dafür verantwortlich, sicherzustellen, dass alle Ihre Klassen ineinander eingebettet sind.
Kehren wir zurück zur BeanFactory
Da Sie sich jetzt mit BPP auskennen, müssen Sie klarstellen, dass bei der Iteration überHashMap
„s“ BeanDefinition
alle „s“ zunächst separat erstellt und platziert werden (nicht im IoC-Container) BeanPostProcessor
. Danach werden reguläre Beans unserer Geschäftslogik erstellt, in einen IoC-Container gestellt und ihre Konfiguration beginnt mithilfe separat verzögerter BPPs. Und so passiert es, jedes BPP verfügt über 2 Methoden:
postProcessorBeforeInitialization(Object bean, String beanName);
postProcessorAfterInitialization(Object bean, String beanName);
Durchläuft unsere Behälter zweimal. Beim ersten Mal wird die Methode aufgerufen postProcessorBeforeInitialization
und beim zweiten Mal wird die Methode aufgerufen postProcessorAfterInitialization
. Sicherlich hat sich die Frage gestellt, warum zwei Methoden benötigt werden, lassen Sie mich das erklären. Tatsache ist, dass zur Verarbeitung einiger Annotationen (z. @Transactional
B.) unsere Bean durch eine Proxy-Klasse ersetzt wird. Um zu verstehen, warum dies geschieht, müssen Sie wissen, wie es funktioniert @Transactional
, und zwar so. Sie müssen der mit dieser Annotation markierten Methode im laufenden Betrieb ein paar weitere Codezeilen hinzufügen. Wie kann man das machen? Richtig, indem Sie eine Proxy-Klasse erstellen, in der der erforderliche Code zur erforderlichen Methode hinzugefügt wird. Stellen Sie sich nun diese Situation vor, wir haben eine Klasse:
class A {
@Autowired
private SomeClass someClass;
@Transactional
public void method() {
// модификатор доступа обязательно public
}
}
Diese Klasse hat 2 Anmerkungen @Autowired
und @Transactional
. Beide Annotationen werden von unterschiedlichen BPPs verarbeitet. Wenn es zuerst funktioniert AutowiredBPP
, ist alles in Ordnung, aber wenn nicht, werden wir auf dieses Problem stoßen. Tatsache ist, dass beim Erstellen der Proxy-Klasse alle Metainformationen verloren gehen. Mit anderen Worten: Es gibt keine Informationen über die Annotation @Autowired
in der Proxy-Klasse und daher AutowiredBPP
wird sie nicht funktionieren, was bedeutet, dass unser Feld someClass
den Wert hat null
, was höchstwahrscheinlich zu einem NPE führen wird. Es ist auch wichtig zu wissen, dass zwischen Methodenaufrufen die -Methode aufgerufen postProcessorBeforeInitialization
wird , sofern vorhanden. Dies ist im Grunde der zweite Konstruktor, aber der Unterschied besteht darin, dass zu diesem Zeitpunkt alle unsere Abhängigkeiten bereits in die Klasse eingebettet sind und wir über die -Methode auf sie zugreifen können. Also noch einmal der Kontextinitialisierungsalgorithmus: postProcessorAfterInitialization
init
init
XmlBeanDefinitionReader
scannt unsere XML-Konfigurationsdatei.- Erstellt
BeanDefinition
's und fügt sie einHashMap
. - Kommt und addiert
BeanFactory
daraus separat alle 's.HashMap
BeanPostProcessor
- Erstellt
BeanDefinition
Beans aus 's und platziert sie in einem IoC-Container. - Hier kommen BPPs und konfigurieren diese Beans mit zwei Methoden.
- Bereit.
GO TO FULL VERSION