Cześć! Podczas gdy administracja JavaRush pracuje nad nowymi poziomami, chcę rozpocząć serię artykułów szkoleniowych na temat Spring Framework. Tak, wiem, że w Internecie jest już mnóstwo materiałów na ten temat, jednak jak pokazuje praktyka, wszystkie są na poziomie Hello World. Chcę porozmawiać nie o tym, jak poprawnie umieszczać adnotacje, ale o tym, jak to wszystko działa „pod maską”. Artykuł jest przeznaczony dla tych, którzy już w taki czy inny sposób pracowali z tym frameworkiem i są zaznajomieni z podstawowymi pojęciami.
Inicjowanie kontekstu.
Zacznijmy więc od podstaw. Moim zdaniem jedną z najważniejszych kwestii jest zrozumienie, w jaki sposób tworzony jest kontekst i inicjowane są komponenty bean. Jak wiadomo, zanim Spring zacznie działać, należy go skonfigurować. W czasach przedpotopowych robiono to za pomocą plików XML (w niektórych projektach, głównie starych, robi się to do dziś). Oto mały przykład takiego pliku konfiguracyjnego:<?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>
Ogólnie rzecz biorąc, wystarczy utworzyć kilka kontrolerów i uruchomić startup (który nie wystartuje). Ale w jaki sposób ta konfiguracja sprawi, że Spring będzie działał? I tu zaczyna się robić ciekawie. Aby nasza konfiguracja była zrozumiała dla Springa, istnieje plik XmlBeanDefinitionReader
. BeanDefinition
Jest to wewnętrzny komponent Springa, który skanuje (parsuje) XML i tworzy pliki XML na podstawie tego, co tam napisaliśmy . BeanDefinition
to obiekt przechowujący informacje o fasoli. Obejmuje to: z jakiej klasy powinien zostać utworzony komponent bean; zakres; czy zainstalowana jest leniwa inicjalizacja; Czy konieczne jest zainicjowanie przed tym komponentem bean innych i innych właściwości opisanych w xml. Wszystkie otrzymane BeanDefinition
''' są dodawane do HashMap
, w którym identyfikatorem jest nazwa komponentu bean (ustawiona przez Ciebie lub nadana przez Springa) oraz BeanDefinition
sam obiekt. Gdy wszystko BeanDefinition
jest już gotowe, na scenie pojawia się nowy bohater – BeanFactory
. Obiekt ten iteruje po HashMap’e
s BeanDefinition
, tworzy na ich podstawie komponenty bean i umieszcza je w kontenerze IoC. Jest tu pewien niuans, tak naprawdę po uruchomieniu aplikacji kontener IoC będzie zawierał komponenty bean, które mają zakres Singleton (domyślnie ustawiony), natomiast pozostałe tworzone są wtedy, gdy są potrzebne (prototyp, żądanie, sesja). A teraz mała dygresja, zapoznajmy się z inną postacią.
Poznaj BeanPostProcessor. (BPP)
Faktem jest, że komponent bean niekoniecznie jest klasą logiki biznesowej dla Twojej aplikacji. Fasola nazywana jest także fasolą infrastrukturalną. Krótko mówiąc, komponent bean infrastruktury to klasa, która konfiguruje komponenty logiki biznesowej (tak, za dużo komponentów bean). Opowiem Wam o tym więcej poniżej, ale żeby było trochę jaśniej, co dokładnie konfiguruje BPP, podam przykład. Czy wszyscy znają podsumowanie@Autowired
? Jesteś zatem AutowiredAnnotationBeanPostProcessor
odpowiedzialny za upewnienie się, że wszystkie klasy są ze sobą osadzone.
Wróćmy do BeanFactory
Wiedząc teraz o BPP, musisz wyjaśnić, że podczas iteracji poHashMap
„, BeanDefinition
wszystkie” są najpierw tworzone i umieszczane osobno (nie w kontenerze IoC) BeanPostProcessor
. Następnie tworzone są regularne ziarna naszej logiki biznesowej, umieszczane w kontenerze IoC i rozpoczynana jest ich konfiguracja przy użyciu oddzielnie odroczonych BPP. A tak to się dzieje, każdy BPP ma 2 metody:
postProcessorBeforeInitialization(Object bean, String beanName);
postProcessorAfterInitialization(Object bean, String beanName);
Dwukrotnie przegląda nasze pojemniki. Przy pierwszym wywołaniu metody postProcessorBeforeInitialization
i przy drugim wywołaniu metody postProcessorAfterInitialization
. Z pewnością pojawiło się pytanie, dlaczego potrzebne są dwie metody, pozwólcie, że wyjaśnię. Faktem jest, że aby przetworzyć niektóre adnotacje (takie jak @Transactional
np. ), nasz komponent bean zostaje zastąpiony klasą proxy. Aby zrozumieć, dlaczego tak się dzieje, musisz wiedzieć, jak to działa @Transactional
i tak to działa. Musisz na bieżąco dodać jeszcze kilka linijek kodu do metody oznaczonej tą adnotacją. Jak to zrobić? Zgadza się, tworząc klasę proxy, wewnątrz której niezbędny kod zostanie dodany do wymaganej metody. Teraz wyobraźmy sobie taką sytuację, mamy klasę:
class A {
@Autowired
private SomeClass someClass;
@Transactional
public void method() {
// модификатор доступа обязательно public
}
}
Ta klasa ma 2 adnotacje @Autowired
i @Transactional
. Obie adnotacje są przetwarzane przez różne procesy BPP. Jeśli to zadziała najpierw AutowiredBPP
, wszystko będzie dobrze, ale jeśli nie, napotkamy ten problem. Faktem jest, że po utworzeniu klasy proxy wszystkie metainformacje zostają utracone. @Autowired
Innymi słowy, w klasie proxy nie będzie informacji o adnotacji , a co za tym idzie AutowiredBPP
, nie będzie ona działać, co oznacza, że nasze pole someClass
będzie miało wartość null
, co najprawdopodobniej doprowadzi do NPE. Warto też wiedzieć, że pomiędzy wywołaniami metod wywoływana postProcessorBeforeInitialization
jest metoda -o ile taka istnieje. Jest to w zasadzie drugi konstruktor, z tą różnicą, że w tym momencie wszystkie nasze zależności są już osadzone w klasie i mamy do nich dostęp poprzez metodę -. Zatem jeszcze raz algorytm inicjalizacji kontekstu: postProcessorAfterInitialization
init
init
XmlBeanDefinitionReader
skanuje nasz plik konfiguracyjny XML.- Tworzy
BeanDefinition
i umieszcza jeHashMap
. - Przychodzi
BeanFactory
i z tegoHashMap
oddzielnie sumuje się wszystkieBeanPostProcessor
„”. - Tworzy komponenty
BeanDefinition
bean z 's i umieszcza je w kontenerze IoC. - Tutaj BPP przychodzą i konfigurują te komponenty bean przy użyciu 2 metod.
- Gotowy.
GO TO FULL VERSION